You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2558 lines
90 KiB
2558 lines
90 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
Additional permissions under GNU GPL version 3 section 7 |
|
|
|
If you modify this Program, or any covered work, by linking or |
|
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK, |
|
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent |
|
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK |
|
(or a modified version of those libraries), |
|
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA, |
|
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG |
|
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the |
|
licensors of this Program grant you additional |
|
permission to convey the resulting work. Corresponding Source for a |
|
non-source form of such a combination shall include the source code for |
|
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered |
|
work. |
|
|
|
You can contact Cyan Worlds, Inc. by email legal@cyan.com |
|
or by snail mail at: |
|
Cyan Worlds, Inc. |
|
14617 N Newport Hwy |
|
Mead, WA 99021 |
|
|
|
*==LICENSE==*/ |
|
////////////////////////////////////////////////////////////////////////////// |
|
// // |
|
// plMeshConverter Class Functions // |
|
// // |
|
//// Version History ///////////////////////////////////////////////////////// |
|
// // |
|
// Created 4.18.2001 mcn // |
|
// // |
|
////////////////////////////////////////////////////////////////////////////// |
|
|
|
#include "HeadSpin.h" |
|
#include "hsBitVector.h" |
|
#include "hsExceptionStack.h" |
|
#include "hsResMgr.h" |
|
#include "hsTemplates.h" |
|
#include "plTweak.h" |
|
#include "hsWindows.h" |
|
|
|
#include <max.h> |
|
#include <iparamb2.h> |
|
#include <modstack.h> |
|
#include <ISkin.h> |
|
#include <meshdlib.h> |
|
#include <stdmat.h> |
|
#pragma hdrstop |
|
|
|
#include "plMeshConverter.h" |
|
#include "MaxMain/plMaxNode.h" |
|
#include "MaxExport/plErrorMsg.h" |
|
#include "plSurface/hsGMaterial.h" |
|
#include "plSurface/plLayerInterface.h" |
|
#include "plDrawable/plGeometrySpan.h" |
|
#include "hsConverterUtils.h" |
|
#include "hsMaterialConverter.h" |
|
#include "hsControlConverter.h" |
|
#include "MaxPlasmaMtls/Materials/plCompositeMtl.h" |
|
#include "MaxPlasmaMtls/Materials/plPassMtl.h" |
|
#include "MaxPlasmaMtls/Materials/plCompositeMtlPB.h" |
|
#include "MaxPlasmaMtls/Materials/plPassMtlBasicPB.h" |
|
#include "plDrawable/plGBufferGroup.h" |
|
#include "plParticleSystem/plConvexVolume.h" |
|
#include "plDrawable/plGeoSpanDice.h" |
|
|
|
#include "plDrawable/plAccessGeometry.h" |
|
#include "plDrawable/plAccessSpan.h" |
|
#include "plDrawable/plAccessTriSpan.h" |
|
#include "plDrawable/plAccessVtxSpan.h" |
|
|
|
#include "plStatusLog/plStatusLog.h" |
|
|
|
//// Static Members ////////////////////////////////////////////////////////// |
|
|
|
bool plMeshConverter::fWarnBadNormals = true; |
|
char plMeshConverter::fWarnBadNormalsMsg[] = "Bad normal autogeneration - please deliver Max file to QA"; |
|
|
|
bool plMeshConverter::fWarnBadUVs = true; |
|
char plMeshConverter::fWarnBadUVsMsg[] = "The object \"%s\" does not have enough valid UVW mapping channels \ |
|
for the material(s) applied to it. This might produce unwanted rendering artifacts at runtime"; |
|
|
|
bool plMeshConverter::fWarnSuspiciousUVs = true; |
|
char plMeshConverter::fWarnSuspiciousUVsMsg[] = "The object \"%s\" has suspicious UVW coordinates on it. \ |
|
You should apply an Unwrap UVW modifier to it."; |
|
|
|
char plMeshConverter::fTooManyVertsMsg[] = "The mesh \"%s\" has too many vertices to fit into a single buffer. \ |
|
Please break up the mesh into pieces with no more than %u vertices each\ |
|
or apply optimize terrain."; |
|
|
|
char plMeshConverter::fTooManyFacesMsg[] = "The mesh \"%s\" has too many faces to fit into a single buffer. \ |
|
Please break up the mesh into pieces with no more than %u faces each\ |
|
or apply optimize terrain."; |
|
|
|
|
|
//// Local Helper Class Definitions ////////////////////////////////////////// |
|
|
|
class TempWeightInfo |
|
{ |
|
public: |
|
float fWeights[ 4 ]; |
|
uint32_t fIndices; |
|
}; |
|
|
|
|
|
class plMAXVertexAccNode |
|
{ |
|
public: |
|
hsPoint3 fPoint; // Inefficient space-wise, I know, but it makes this a lot simpler... |
|
hsVector3 fNormal; |
|
hsColorRGBA fColor, fIllum; |
|
uint32_t fIndex; |
|
hsPoint3 fUVs[ plGeometrySpan::kMaxNumUVChannels ]; |
|
uint32_t fNumChannels; |
|
|
|
plMAXVertexAccNode *fNext; |
|
|
|
plMAXVertexAccNode( const hsPoint3 *point, const hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, int numChannels, const hsPoint3 *uvs, uint32_t index ); |
|
|
|
bool IsEqual( const hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, const hsPoint3 *uvs ); |
|
}; |
|
|
|
typedef plMAXVertexAccNode *plMAXVertexAccNodePtr; |
|
|
|
class plMAXVertexAccumulator |
|
{ |
|
protected: |
|
|
|
int fNumPoints, fNumChannels, fNumVertices; |
|
plMAXVertexAccNode **fPointList; |
|
|
|
hsTArray<uint32_t> fIndices; |
|
hsTArray<uint32_t> fInverseVertTable; |
|
|
|
void IFindSkinWeights( ISkinContextData *skinData, int vertex, float *weights, uint32_t *indices ); |
|
void IFindUserSkinWeights( plMaxNode* node, Mesh* mesh, int vertex, float *weights, uint32_t *indices ); |
|
void IFindAllUserSkinWeights( plMaxNode* node, Mesh* mesh, TempWeightInfo weights[]); |
|
public: |
|
|
|
plMAXVertexAccumulator( int numOrigPoints, int numChannels ); |
|
~plMAXVertexAccumulator(); |
|
|
|
void AddVertex( int index, hsPoint3 *point, hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, hsPoint3 uvs[ plGeometrySpan::kMaxNumUVChannels ] ); |
|
|
|
void StuffMyData( plMaxNode* node, plGeometrySpan *span, Mesh* mesh, ISkinContextData* skinData ); |
|
|
|
int GetVertexCount(); |
|
uint32_t GetIndexCount( void ) { return fIndices.GetCount(); } |
|
}; |
|
|
|
class plMAXVertNormal |
|
{ |
|
public: |
|
Point3 fNormal; |
|
DWORD fSmGroup; |
|
bool fInited; |
|
|
|
plMAXVertNormal *fNext; |
|
|
|
plMAXVertNormal() { fSmGroup = 0; fNext = nil; fInited = false; fNormal = Point3( 0, 0, 0 ); } |
|
plMAXVertNormal( Point3 &n, DWORD s ) { fNext = nil; fInited = true; fNormal = n; fSmGroup = s; } |
|
~plMAXVertNormal() { /*delete fNext; */} |
|
void DestroyChain( void ) { if( fNext != nil ) fNext->DestroyChain(); delete fNext; fNext = nil; } |
|
|
|
// Adding normalization of input n. Input is usually just crossproduct of face edges. Non-normalized, |
|
// large faces will overwhelm small faces on summation, which is the opposite of what we want, since |
|
// curvature is most accurately captured in the small faces. mf. |
|
void AddNormal( Point3 &n, DWORD s ) |
|
{ |
|
if( !( s & fSmGroup ) && fInited ) |
|
{ |
|
if( fNext ) |
|
fNext->AddNormal( n, s ); |
|
else |
|
fNext = new plMAXVertNormal( ::Normalize(n), s ); |
|
} |
|
else |
|
{ |
|
fNormal += ::Normalize(n); |
|
fSmGroup |= s; |
|
fInited = true; |
|
} |
|
} |
|
|
|
Point3 &GetNormal( DWORD s ) |
|
{ |
|
if( ( fSmGroup & s ) || ( fNext == nil ) ) |
|
return fNormal; |
|
else |
|
return fNext->GetNormal( s ); |
|
} |
|
|
|
/// Relies on static variable, values will be destroyed between calls |
|
hsVector3 &GetPlNormal( DWORD s ) |
|
{ |
|
Point3 n = GetNormal( s ); |
|
static hsVector3 pt; |
|
pt.Set( n.x, n.y, n.z ); |
|
return pt; |
|
} |
|
|
|
void Normalize( void ) |
|
{ |
|
plMAXVertNormal *ptr = fNext, *prev = this; |
|
|
|
|
|
while( ptr != nil ) |
|
{ |
|
if( ptr->fSmGroup & fSmGroup ) |
|
{ |
|
fNormal += ptr->fNormal; |
|
prev->fNext = ptr->fNext; |
|
delete ptr; |
|
ptr = prev->fNext; |
|
} |
|
else |
|
{ |
|
prev = ptr; |
|
ptr = ptr->fNext; |
|
} |
|
} |
|
|
|
fNormal = ::Normalize( fNormal ); |
|
if( fNext ) |
|
fNext->Normalize(); |
|
} |
|
}; |
|
|
|
//// Instance and Constructor/Destructor ///////////////////////////////////// |
|
|
|
plMeshConverter& plMeshConverter::Instance() |
|
{ |
|
static plMeshConverter the_instance; |
|
return the_instance; |
|
} |
|
|
|
plMeshConverter::plMeshConverter() : |
|
fInterface(nil), |
|
fConverterUtils(hsConverterUtils::Instance()), |
|
fIsInitialized(false) |
|
{ |
|
hsGuardBegin("plMeshConverter::plMeshConverter"); |
|
hsGuardEnd; |
|
} |
|
|
|
plMeshConverter::~plMeshConverter() |
|
{ |
|
hsGuardBegin("plMeshConverter::~plMeshConverter"); |
|
hsGuardEnd; |
|
} |
|
|
|
//// Init and DeInit ///////////////////////////////////////////////////////// |
|
|
|
void plMeshConverter::Init( bool save, plErrorMsg *msg ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::Init" ); |
|
|
|
if( fIsInitialized ) |
|
DeInit( false ); |
|
|
|
fIsInitialized = true; |
|
fInterface = GetCOREInterface(); |
|
fErrorMsg = msg; |
|
fWarnBadUVs = true; |
|
fWarnSuspiciousUVs = true; |
|
fWarnBadNormals = true; |
|
|
|
hsGuardEnd; |
|
} |
|
|
|
void plMeshConverter::DeInit( bool deInitLongRecur ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::DeInit" ); |
|
|
|
fIsInitialized = false; |
|
|
|
hsGuardEnd; |
|
} |
|
|
|
void plMeshConverter::StuffPositionsAndNormals(plMaxNode *node, hsTArray<hsPoint3> *pos, hsTArray<hsVector3> *normals) |
|
{ |
|
hsGuardBegin( "plMeshConverter::BuildNormalsArray" ); |
|
|
|
const char* dbgNodeName = node->GetName(); |
|
Mesh *mesh; |
|
int32_t numVerts; |
|
hsMatrix44 l2wMatrix, vert2LMatrix, vertInvTransMatrix, tempMatrix; |
|
|
|
/// Get da mesh |
|
mesh = IGetNodeMesh( node ); |
|
if( mesh == nil ) |
|
return ; |
|
|
|
numVerts = mesh->getNumVerts(); |
|
|
|
/// Get transforms |
|
l2wMatrix = node->GetLocalToWorld44(); |
|
vert2LMatrix = node->GetVertToLocal44(); // vert2LMatrix is the transform we apply |
|
// now to the verts, to get into *our* object-local-space |
|
vert2LMatrix.GetInverse( &tempMatrix ); |
|
tempMatrix.GetTranspose( &vertInvTransMatrix ); // Inverse-transpose of the vert2Local matrix, |
|
// for xforming the normals |
|
mesh->buildNormals(); |
|
|
|
normals->SetCount(numVerts); |
|
pos->SetCount(numVerts); |
|
int i; |
|
for (i = 0; i < numVerts; i++) |
|
{ |
|
// positions |
|
hsPoint3 currPos; |
|
currPos.Set(mesh->verts[i].x, mesh->verts[i].y, mesh->verts[i].z); |
|
pos->Set(i, vert2LMatrix * currPos); |
|
|
|
// normals |
|
RVertex &rv = mesh->getRVert(i); |
|
Point3& norm = rv.rn.getNormal(); |
|
hsVector3 currNorm(norm.x, norm.y, norm.z); |
|
currNorm.Normalize(); |
|
normals->Set(i, vertInvTransMatrix * currNorm); |
|
} |
|
|
|
IDeleteTempGeometry(); |
|
hsGuardEnd; |
|
} |
|
|
|
plConvexVolume *plMeshConverter::CreateConvexVolume(plMaxNode *node) |
|
{ |
|
hsGuardBegin( "plMeshConverter::CreateConvexVolume" ); |
|
|
|
const char* dbgNodeName = node->GetName(); |
|
Mesh *mesh; |
|
int32_t numFaces, i, j, numVerts; |
|
hsMatrix44 l2wMatrix, vert2LMatrix, vertInvTransMatrix, tempMatrix; |
|
bool flipOrder, checkForOverflow = false; |
|
|
|
/// Get da mesh |
|
mesh = IGetNodeMesh( node ); |
|
if( mesh == nil ) |
|
return nil; |
|
|
|
numFaces = mesh->getNumFaces(); |
|
numVerts = mesh->getNumVerts(); |
|
|
|
plConvexVolume *bounds = new plConvexVolume(); |
|
|
|
/// Get transforms |
|
l2wMatrix = node->GetLocalToWorld44(); |
|
vert2LMatrix = node->GetVertToLocal44(); |
|
flipOrder = vert2LMatrix.GetParity(); // vert2LMatrix is the transform we apply |
|
// now to the verts, to get into *our* object-local-space |
|
vert2LMatrix.GetInverse( &tempMatrix ); |
|
tempMatrix.GetTranspose( &vertInvTransMatrix ); // Inverse-transpose of the vert2Local matrix, |
|
// for xforming the normals |
|
|
|
|
|
//mesh->buildNormals(); |
|
|
|
for( i = 0; i < numFaces; i++ ) |
|
{ |
|
Face *maxFace = &mesh->faces[ i ]; |
|
TVFace *maxColorFace = ( mesh->vcFace != nil ) ? &mesh->vcFace[ i ] : nil; |
|
hsPoint3 pos[ 3 ]; |
|
hsPoint3 testPt1, testPt2, testPt3; |
|
hsVector3 normal; |
|
uint32_t vertIdx[ 3 ]; |
|
|
|
/// Add the 3 vertices to the correct vertex accumulator object |
|
|
|
// Get positions |
|
if( flipOrder ) |
|
{ |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
vertIdx[ j ] = maxFace->getVert( 2 - j ); |
|
pos[ j ].fX = mesh->verts[ vertIdx[ j ] ].x; |
|
pos[ j ].fY = mesh->verts[ vertIdx[ j ] ].y; |
|
pos[ j ].fZ = mesh->verts[ vertIdx[ j ] ].z; |
|
} |
|
} |
|
else |
|
{ |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
vertIdx[ j ] = maxFace->getVert( j ); |
|
pos[ j ].fX = mesh->verts[ vertIdx[ j ] ].x; |
|
pos[ j ].fY = mesh->verts[ vertIdx[ j ] ].y; |
|
pos[ j ].fZ = mesh->verts[ vertIdx[ j ] ].z; |
|
} |
|
} |
|
|
|
// Look for degenerate triangles (why MAX even gives us these I have no clue...) |
|
testPt1 = pos[ 1 ] - pos[ 0 ]; |
|
testPt2 = pos[ 2 ] - pos[ 0 ]; |
|
testPt3 = pos[ 2 ] - pos[ 1 ]; |
|
if( ( testPt1.fX == 0.0f && testPt1.fY == 0.0f && testPt1.fZ == 0.0f ) || |
|
( testPt2.fX == 0.0f && testPt2.fY == 0.0f && testPt2.fZ == 0.0f ) || |
|
( testPt3.fX == 0.0f && testPt3.fY == 0.0f && testPt3.fZ == 0.0f ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// Translate to local space |
|
for( j = 0; j < 3; j++ ) |
|
pos[ j ] = vert2LMatrix * pos[ j ]; |
|
|
|
// Calculate normal for face |
|
hsVector3 v1, v2; |
|
v1.Set( &pos[ 1 ], &pos[ 0 ] ); |
|
v2.Set( &pos[ 2 ], &pos[ 0 ] ); |
|
normal = (v1 % v2); |
|
normal.Normalize(); |
|
|
|
hsPlane3 plane(&normal, normal.InnerProduct(pos[0])); |
|
bounds->AddPlane(plane); // auto-checks for redundant planes. |
|
} |
|
|
|
IDeleteTempGeometry(); |
|
|
|
return bounds; |
|
hsGuardEnd; |
|
} |
|
|
|
// |
|
// Sometimes objects can have faces without UV coordinates. These faces will |
|
// export random UV values each time we export, changing the data and forcing |
|
// patches when they aren't necessary. To detect these, we put a Unwrap UVW mod |
|
// on the object and see if it changes the UV values. |
|
// |
|
bool plMeshConverter::IValidateUVs(plMaxNode* node) |
|
{ |
|
if (node->GetObjectRef()->SuperClassID() != GEN_DERIVOB_CLASS_ID) |
|
return true; |
|
|
|
Mesh* mesh = IGetNodeMesh(node); |
|
if (!mesh) |
|
return true; |
|
|
|
if (mesh->getNumMaps() < 2) |
|
return true; |
|
|
|
// Cache the original UV verts |
|
int numVerts = mesh->getNumMapVerts(1); |
|
int vertBufSize = sizeof(UVVert)*numVerts; |
|
UVVert* origVerts = new UVVert[vertBufSize]; |
|
memcpy(origVerts, mesh->mapVerts(1), vertBufSize); |
|
|
|
IDeleteTempGeometry(); |
|
|
|
// Add an Unwrap UVW mod onto the stack |
|
IDerivedObject* derivedObject = (IDerivedObject*)node->GetObjectRef(); |
|
#define UNWRAP_UVW_CID Class_ID(0x02df2e3a, 0x72ba4e1f) |
|
Modifier* mod = (Modifier*)GetCOREInterface()->CreateInstance(OSM_CLASS_ID, UNWRAP_UVW_CID); |
|
derivedObject->AddModifier(mod); |
|
|
|
mesh = IGetNodeMesh(node); |
|
|
|
bool uvsAreBad = false; |
|
|
|
UVVert* newVerts = mesh->mapVerts(1); |
|
for (int i = 0; i < numVerts; i++) |
|
{ |
|
UVVert uvDiff = newVerts[i] - origVerts[i]; |
|
float diff = uvDiff.Length(); |
|
if (diff > 0.01) |
|
{ |
|
uvsAreBad = true; |
|
break; |
|
} |
|
} |
|
|
|
delete [] origVerts; |
|
IDeleteTempGeometry(); |
|
|
|
derivedObject->DeleteModifier(); |
|
|
|
if (uvsAreBad) |
|
{ |
|
plFileName logfile = plFormat("UV_{}.log", GetCOREInterface()->GetCurFileName().data()); |
|
plStatusLog::AddLineS(logfile, "%s has suspicious UVs", node->GetName()); |
|
|
|
if (fWarnSuspiciousUVs) |
|
{ |
|
/// We're missing some UV channels on our object. We'll handle it later; warn the user here |
|
if (fErrorMsg->Set(true, "UVW Warning", fWarnSuspiciousUVsMsg, node->GetName()).CheckAskOrCancel()) |
|
fWarnSuspiciousUVs = false; |
|
fErrorMsg->Set(false); |
|
} |
|
} |
|
|
|
return uvsAreBad; |
|
} |
|
|
|
//// CreateSpans ///////////////////////////////////////////////////////////// |
|
// Main function. Takes a maxNode's object and creates geometrySpans from it |
|
// suitable for drawing as ice. |
|
|
|
bool plMeshConverter::CreateSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray, bool doPreshading ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::CreateSpans" ); |
|
|
|
const char* dbgNodeName = node->GetName(); |
|
Mesh *mesh; |
|
int32_t numFaces, i, j, k, numVerts, maxNumBones, maxUVWSrc; |
|
int32_t numMaterials = 1, numSubMaterials = 1; |
|
hsMatrix44 l2wMatrix, vert2LMatrix, vertInvTransMatrix, tempMatrix; |
|
Mtl *maxMaterial = nil; |
|
bool isComposite, isMultiMat, flipOrder, checkForOverflow = false, includesComp; |
|
uint8_t ourFormat, numChannels, maxBlendChannels; |
|
hsColorRGBA *colorArray = nil; |
|
hsColorRGBA *illumArray = nil; |
|
uint32_t sharedSpanProps = 0; |
|
hsBitVector usedSubMtls; |
|
bool makeAlphaLayer = node->VtxAlphaNotAvailable(); |
|
|
|
ISkinContextData *skinData; |
|
|
|
hsTArray<hsTArray<plExportMaterialData> *> ourMaterials; |
|
hsTArray<hsTArray<plMAXVertexAccumulator *> *> ourAccumulators; |
|
|
|
hsTArray<plMAXVertNormal> vertNormalCache; |
|
hsTArray<plMAXVertNormal>* vertDPosDuCache = nil; |
|
hsTArray<plMAXVertNormal>* vertDPosDvCache = nil; |
|
|
|
//// Setup /////////////////////////////////////////////////////////////// |
|
plLocation nodeLoc = node->GetLocation(); |
|
|
|
TimeValue timeVal = fConverterUtils.GetTime(fInterface); |
|
Class_ID cid = node->EvalWorldState(timeVal).obj->ClassID(); |
|
if( node->EvalWorldState(timeVal).obj->ClassID() == BONE_OBJ_CLASSID ) |
|
return false; |
|
|
|
IValidateUVs(node); |
|
|
|
/// Get da mesh |
|
mesh = IGetNodeMesh( node ); |
|
if( mesh == nil ) |
|
return false; |
|
numFaces = mesh->getNumFaces(); |
|
numVerts = mesh->getNumVerts(); |
|
|
|
/// Get the material |
|
maxMaterial = hsMaterialConverter::Instance().GetBaseMtl( node ); |
|
isMultiMat = hsMaterialConverter::Instance().IsMultiMat( maxMaterial ); |
|
|
|
const bool smoothAll = node->GetSmoothAll(); |
|
|
|
includesComp = false; |
|
if (isMultiMat) |
|
{ |
|
for (i = 0; i < numFaces; i++) |
|
{ |
|
int index = mesh->faces[i].getMatID(); |
|
if (index >= maxMaterial->NumSubMtls()) |
|
index = 0; |
|
|
|
usedSubMtls.SetBit(index); |
|
if (hsMaterialConverter::Instance().IsCompositeMat(maxMaterial->GetSubMtl(index))) |
|
includesComp = true; |
|
} |
|
} |
|
else |
|
includesComp = hsMaterialConverter::Instance().IsCompositeMat(maxMaterial); |
|
|
|
try |
|
{ |
|
|
|
/// Check vert count |
|
if( numVerts >= plGBufferGroup::kMaxNumVertsPerBuffer || numFaces * 3 >= plGBufferGroup::kMaxNumIndicesPerBuffer ) |
|
{ |
|
/// Possible overflow, but not sure. Only check for overflow if this is set |
|
checkForOverflow = true; |
|
} |
|
|
|
/// Get transforms |
|
l2wMatrix = node->GetLocalToWorld44(); |
|
vert2LMatrix = node->GetVertToLocal44(); |
|
flipOrder = vert2LMatrix.GetParity(); // vert2LMatrix is the transform we apply |
|
// now to the verts, to get into *our* object-local-space |
|
vert2LMatrix.GetInverse( &tempMatrix ); |
|
tempMatrix.GetTranspose( &vertInvTransMatrix ); // Inverse-transpose of the vert2Local matrix, |
|
// for xforming the normals |
|
|
|
// OTM used in generating normals. |
|
Matrix3 otm = node->GetOTM(); |
|
Matrix3 invOtm = Inverse(otm); |
|
invOtm.SetTrans(Point3(0,0,0)); |
|
invOtm.ValidateFlags(); |
|
|
|
// If we use a composite on this object, we don't want to export the illumination channel. |
|
UVVert *illumMap = mesh->mapVerts(MAP_SHADING); |
|
int numIllumVerts = mesh->getNumMapVerts(MAP_SHADING); |
|
|
|
UVVert *alphaMap = mesh->mapVerts(MAP_ALPHA); |
|
int numAlphaVerts = mesh->getNumMapVerts(MAP_ALPHA); |
|
|
|
if( node->GetRunTimeLight() ) |
|
{ |
|
sharedSpanProps |= plGeometrySpan::kPropRunTimeLight; |
|
} |
|
if( node->GetNoPreShade() ) |
|
{ |
|
sharedSpanProps |= plGeometrySpan::kPropNoPreShade; |
|
} |
|
float waterHeight = 0; |
|
if( node->GetHasWaterHeight() ) |
|
{ |
|
sharedSpanProps |= plGeometrySpan::kWaterHeight; |
|
waterHeight = node->GetWaterHeight(); |
|
} |
|
/// Which lighting equation? |
|
if( node->NonVtxPreshaded() ) |
|
{ |
|
/// OK, we can go with kLiteVtxNonPreshaded, so we get vertex alpha. Yipee!!! |
|
sharedSpanProps |= plGeometrySpan::kLiteVtxNonPreshaded; |
|
} |
|
|
|
//// Vertex Colors / Illumination //////////////////////////////////////// |
|
|
|
/// If there are colors, pre-convert them |
|
hsColorRGBA white, black; |
|
white.Set(1.f, 1.f, 1.f, 1.f); |
|
black.Set(0, 0, 0, 1.f); |
|
bool allWhite = true, allBlack = true; |
|
|
|
if( mesh->numCVerts > 0) |
|
{ |
|
if (mesh->vertCol != nil) |
|
{ |
|
colorArray = new hsColorRGBA[ mesh->numCVerts ]; |
|
for( i = 0; i < mesh->numCVerts; i++ ) |
|
{ |
|
colorArray[i].Set(mesh->vertCol[ i ].x, mesh->vertCol[ i ].y, mesh->vertCol[ i ].z, 1.f); |
|
if (colorArray[ i ] != black) |
|
allBlack = false; |
|
} |
|
|
|
// XXX Sometimes 3DS reports that all colors have been set black (when they haven't been touched). |
|
// We set them white here, so that they don't affect the shader when multiplied in. |
|
// (Sometimes it reports them as all white too, but hey, that's the value we'd use anyway...) |
|
if (allBlack) |
|
for( i = 0; i < mesh->numCVerts; i++ ) |
|
colorArray[ i ] = white; |
|
} |
|
} |
|
|
|
if (illumMap != nil) |
|
{ |
|
// MF_HORSE CARNAGE |
|
illumArray = new hsColorRGBA[numIllumVerts]; |
|
for( i = 0; i < numIllumVerts; i++ ) |
|
{ |
|
illumArray[i].Set(illumMap[ i ].x, illumMap[ i ].y, illumMap[ i ].z, 1.f); |
|
if (illumArray[ i ] != white) |
|
allWhite = false; |
|
} |
|
|
|
// XXX Same hack as with colorArray above, except illumination values are added in, so we set them black |
|
// in order to not affect the shader. |
|
if (allWhite) |
|
for( i = 0; i < numIllumVerts; i++ ) |
|
illumArray[ i ] = black; |
|
// MF_HORSE CARNAGE |
|
} |
|
|
|
//// Materials / Mapping Channels Setup ////////////////////////////////// |
|
|
|
numChannels = node->NumUVWChannels(); |
|
|
|
maxBlendChannels = 0; |
|
|
|
if( isMultiMat ) |
|
{ |
|
numMaterials = maxMaterial->NumSubMtls(); |
|
|
|
ourMaterials.SetCountAndZero( numMaterials ); |
|
for( i = 0; i < numMaterials; i++ ) |
|
{ |
|
if (usedSubMtls.IsBitSet(i)) // Only export the sub materials actually used |
|
ourMaterials[i] = hsMaterialConverter::Instance().CreateMaterialArray( maxMaterial->GetSubMtl(i), node, i); |
|
else |
|
ourMaterials[i] = nil; |
|
} |
|
} |
|
else // plPassMtl, plDecalMat, plMultiPassMtl |
|
{ |
|
numMaterials = 1; |
|
|
|
ourMaterials.Reset(); |
|
ourMaterials.Append(hsMaterialConverter::Instance().CreateMaterialArray( maxMaterial, node, 0 )); |
|
} |
|
|
|
/// UV check on the layers |
|
for( i = 0, maxUVWSrc = -1; i < numMaterials; i++ ) |
|
{ |
|
hsTArray<plExportMaterialData> *subMats = ourMaterials[i]; |
|
if (subMats == nil) |
|
continue; |
|
for( j = 0; j < subMats->GetCount(); j++ ) |
|
{ |
|
plExportMaterialData currData = subMats->Get(j); |
|
if (currData.fMaterial == nil) |
|
continue; |
|
|
|
for( k = 0; k < currData.fMaterial->GetNumLayers(); k++ ) |
|
{ |
|
plLayerInterface *layer = currData.fMaterial->GetLayer( k ); |
|
|
|
int uvwSrc = layer->GetUVWSrc() & plLayerInterface::kUVWIdxMask; |
|
|
|
if( maxUVWSrc < uvwSrc && layer->GetTexture() != nil ) |
|
maxUVWSrc = uvwSrc; |
|
if( maxBlendChannels < currData.fNumBlendChannels) |
|
maxBlendChannels = currData.fNumBlendChannels; |
|
} |
|
} |
|
} |
|
// If this node is a water decal set to environment map, then there's only 1 layer, but |
|
// we'll need an extra 2 uvw channels for the tangent space basis vectors. |
|
if( node->GetWaterDecEnv() ) |
|
maxUVWSrc = 2; |
|
|
|
if( numChannels + maxBlendChannels < ( maxUVWSrc + 1 ) && fWarnBadUVs ) |
|
{ |
|
/// We're missing some UV channels on our object. We'll handle it later; warn the user here |
|
if( fErrorMsg->Set( true, "UVW Channel Warning", fWarnBadUVsMsg, node->GetName() ).CheckAskOrCancel() ) |
|
fWarnBadUVs = false; |
|
fErrorMsg->Set( false ); |
|
} |
|
else if( numChannels > ( maxUVWSrc + 1 ) ) |
|
{ |
|
// Make sure we allocate enough for all the channel data, even if the materials don't use them (yet...) |
|
// (trick is, make sure those extra channels are valid first) |
|
for( i = maxUVWSrc + 1; i < numChannels; i++ ) |
|
{ |
|
if( mesh->mapFaces( i + 1 ) == nil ) |
|
{ |
|
numChannels = i; |
|
break; |
|
} |
|
} |
|
|
|
maxUVWSrc = numChannels - 1; |
|
} |
|
|
|
//maxUVWSrc += maxBlendChannels; |
|
if (maxUVWSrc > plGeometrySpan::kMaxNumUVChannels - 1) maxUVWSrc = plGeometrySpan::kMaxNumUVChannels - 1; |
|
|
|
/// Our buffer format... |
|
/* ourFormat = ( maxUVWSrc == -1 ) ? plGeometrySpan::kNoUVChannels : |
|
( maxUVWSrc == 0 ) ? plGeometrySpan::k1UVChannel : |
|
( maxUVWSrc == 1 ) ? plGeometrySpan::k2UVChannels : |
|
( maxUVWSrc == 2 ) ? plGeometrySpan::k3UVChannels : |
|
plGeometrySpan::k4UVChannels; |
|
*/ |
|
ourFormat = plGeometrySpan::UVCountToFormat( maxUVWSrc + 1 ); |
|
/// NOW allocate our accumulators, since maxUVWSrc was just calculated... |
|
ourAccumulators.SetCount( numMaterials ); |
|
for( i = 0; i < numMaterials; i++ ) |
|
{ |
|
if (ourMaterials[i] == nil) |
|
{ |
|
ourAccumulators[i] = nil; |
|
continue; |
|
} |
|
|
|
hsTArray<plMAXVertexAccumulator *> *currAccum = new hsTArray<plMAXVertexAccumulator *>; |
|
int currNumSubMtls = ourMaterials[i]->GetCount(); |
|
currAccum->Reset(); |
|
ourAccumulators[i] = currAccum; |
|
for (j = 0; j < currNumSubMtls; j++) |
|
{ |
|
currAccum->Append(new plMAXVertexAccumulator( mesh->getNumVerts(), maxUVWSrc + 1 )); |
|
} |
|
} |
|
|
|
|
|
//// Skinning //////////////////////////////////////////////////////////// |
|
|
|
/// Check for skinning |
|
ISkin* skin = node->FindSkinModifier(); |
|
if( skin ) |
|
{ |
|
skinData = skin->GetContextInterface(node); |
|
int skinNumPoints = skinData->GetNumPoints(); |
|
if(skinNumPoints != numVerts) |
|
{ |
|
fErrorMsg->Set(true, "Skinning Error", "Invalid point count on ISkin data on node %s", dbgNodeName ).Show(); |
|
fErrorMsg->Set(); |
|
throw (bool)false; |
|
//hsAssert( skinData->GetNumPoints() == numVerts, "Invalid point count on ISkin data" ); |
|
|
|
} |
|
|
|
|
|
/// Loop through the skin verts and find the max # of bones |
|
for( i = 0, maxNumBones = 0; i < numVerts; i++ ) |
|
{ |
|
if( skinData->GetNumAssignedBones( i ) > maxNumBones ) |
|
maxNumBones = skinData->GetNumAssignedBones( i ); |
|
} |
|
maxNumBones++; |
|
if( maxNumBones > 4 ) |
|
maxNumBones = 4; |
|
//hsAssert( maxNumBones >= 2, "Invalid skin (not enough bones)" ); |
|
if( maxNumBones < 2) |
|
{ |
|
fErrorMsg->Set(true, "Skinning Error", "Invalid skin (no bones) on node %s", dbgNodeName ).Show(); |
|
fErrorMsg->Set(); |
|
throw (bool)false; |
|
} |
|
|
|
|
|
if (node->GetBoneMap() && maxNumBones == 2) |
|
maxNumBones++; |
|
|
|
/// Change format to match |
|
ourFormat |= ( maxNumBones == 2 ) ? plGeometrySpan::kSkin1Weight : |
|
( maxNumBones == 3 ) ? plGeometrySpan::kSkin2Weights : plGeometrySpan::kSkin3Weights; |
|
|
|
if( skin->GetNumBones() > 1 || node->GetBoneMap()) |
|
ourFormat |= plGeometrySpan::kSkinIndices; |
|
} |
|
else |
|
{ |
|
skinData = nil; |
|
|
|
if( node->NumBones() ) |
|
{ |
|
maxNumBones = 2; |
|
ourFormat |= plGeometrySpan::kSkin1Weight; |
|
} |
|
} |
|
|
|
|
|
//// Build Vertex Normal Cache /////////////////////////////////////////// |
|
|
|
vertNormalCache.SetCount( mesh->getNumVerts() ); |
|
for( i = 0; i < mesh->getNumFaces(); i++ ) |
|
{ |
|
Face *maxFace = &mesh->faces[ i ]; |
|
Point3 v0, v1, v2, norm; |
|
|
|
uint32_t smGroup = smoothAll ? 1 : maxFace->getSmGroup(); |
|
|
|
v0 = mesh->verts[ maxFace->v[ 0 ] ]; |
|
v1 = mesh->verts[ maxFace->v[ 1 ] ]; |
|
v2 = mesh->verts[ maxFace->v[ 2 ] ]; |
|
|
|
norm = ( v1 - v0 ) ^ ( v2 - v1 ); |
|
for( j = 0; j < 3; j++ ) |
|
vertNormalCache[ maxFace->v[ j ] ].AddNormal( norm, maxFace->smGroup ); |
|
} |
|
for( i = 0; i < vertNormalCache.GetCount(); i++ ) |
|
vertNormalCache[ i ].Normalize(); |
|
|
|
vertDPosDuCache = new hsTArray<plMAXVertNormal>[numMaterials]; |
|
vertDPosDvCache = new hsTArray<plMAXVertNormal>[numMaterials]; |
|
|
|
hsTArray<int16_t> bumpLayIdx; |
|
hsTArray<int16_t> bumpLayChan; |
|
hsTArray<int16_t> bumpDuChan; |
|
hsTArray<int16_t> bumpDvChan; |
|
ISetBumpUvSrcs(ourMaterials, bumpLayIdx, bumpLayChan, bumpDuChan, bumpDvChan); |
|
if( node->GetWaterDecEnv() ) |
|
ISetWaterDecEnvUvSrcs(ourMaterials, bumpLayIdx, bumpLayChan, bumpDuChan, bumpDvChan); |
|
|
|
ISmoothUVGradients(node, mesh, ourMaterials, bumpLayIdx, bumpLayChan, vertDPosDuCache, vertDPosDvCache); |
|
|
|
//// Main Conversion Loop //////////////////////////////////////////////// |
|
|
|
// Loop through the faces and stuff them into spans |
|
spanArray.Reset(); |
|
|
|
mesh->buildNormals(); |
|
|
|
for( i = 0; i < numFaces; i++ ) |
|
{ |
|
Face *maxFace = &mesh->faces[ i ]; |
|
TVFace *maxColorFace = ( mesh->vcFace != nil ) ? &mesh->vcFace[ i ] : nil; |
|
hsPoint3 pos[ 3 ]; |
|
hsPoint3 testPt1, testPt2, testPt3; |
|
hsVector3 normals[ 3 ]; |
|
hsColorRGBA colors[ 3 ], illums[ 3 ]; |
|
uint32_t smGroup, vertIdx[ 3 ]; |
|
hsPoint3 uvs1[ plGeometrySpan::kMaxNumUVChannels + 1]; |
|
hsPoint3 uvs2[ plGeometrySpan::kMaxNumUVChannels + 1]; |
|
hsPoint3 uvs3[ plGeometrySpan::kMaxNumUVChannels + 1]; |
|
hsPoint3 temp; |
|
Mtl *currMaxMtl; |
|
|
|
// The main index is how a multi-material keeps track of which sub material is involved. The sub material |
|
// may actually create multiple materials (like composites), hence the second index, but it most cases it |
|
// will be zero as well. |
|
|
|
int mainMatIndex = 0; |
|
int subMatIndex = 0; |
|
|
|
// Get span index |
|
if( isMultiMat ) |
|
{ |
|
mainMatIndex = maxFace->getMatID(); |
|
if( mainMatIndex >= numMaterials ) |
|
mainMatIndex = 0; |
|
currMaxMtl = maxMaterial->GetSubMtl(mainMatIndex); |
|
} |
|
else |
|
currMaxMtl = maxMaterial; |
|
|
|
int numBlendChannels = 0; |
|
hsTArray<plExportMaterialData> *subMtls = ourMaterials[mainMatIndex]; |
|
hsAssert(subMtls != nil, "Face is assigned a material that we think is unused."); |
|
|
|
for (j = 0; j < subMtls->GetCount(); j++) |
|
{ |
|
int currBlend = subMtls->Get(j).fNumBlendChannels; |
|
if (numBlendChannels < currBlend) |
|
numBlendChannels = currBlend; |
|
} |
|
isComposite = hsMaterialConverter::Instance().IsCompositeMat( currMaxMtl ); |
|
|
|
/// Add the 3 vertices to the correct vertex accumulator object |
|
|
|
// Get positions |
|
if( flipOrder ) |
|
{ |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
vertIdx[ j ] = maxFace->getVert( 2 - j ); |
|
pos[ j ].fX = mesh->verts[ vertIdx[ j ] ].x; |
|
pos[ j ].fY = mesh->verts[ vertIdx[ j ] ].y; |
|
pos[ j ].fZ = mesh->verts[ vertIdx[ j ] ].z; |
|
} |
|
} |
|
else |
|
{ |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
vertIdx[ j ] = maxFace->getVert( j ); |
|
pos[ j ].fX = mesh->verts[ vertIdx[ j ] ].x; |
|
pos[ j ].fY = mesh->verts[ vertIdx[ j ] ].y; |
|
pos[ j ].fZ = mesh->verts[ vertIdx[ j ] ].z; |
|
} |
|
} |
|
|
|
// Look for degenerate triangles (why MAX even gives us these I have no clue...) |
|
testPt1 = pos[ 1 ] - pos[ 0 ]; |
|
testPt2 = pos[ 2 ] - pos[ 0 ]; |
|
testPt3 = pos[ 2 ] - pos[ 1 ]; |
|
if( ( testPt1.fX == 0.0f && testPt1.fY == 0.0f && testPt1.fZ == 0.0f ) || |
|
( testPt2.fX == 0.0f && testPt2.fY == 0.0f && testPt2.fZ == 0.0f ) || |
|
( testPt3.fX == 0.0f && testPt3.fY == 0.0f && testPt3.fZ == 0.0f ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
// If we're expanding the UVW channel list, fill out the rest with zeros |
|
for( j = numChannels; j < maxUVWSrc; j++ ) |
|
{ |
|
uvs1[ j ].Set( 0, 0, 0 ); |
|
uvs2[ j ].Set( 0, 0, 0 ); |
|
uvs3[ j ].Set( 0, 0, 0 ); |
|
} |
|
|
|
// Now for each vertex, get the UVs, calc color, and add |
|
if( numChannels > 0 ) |
|
{ |
|
// Just go ahead and always generate the opacity into the uvs, because we're |
|
// going to look for it there on composites whether they actually use the |
|
// alpha hack texture or not. |
|
IGenerateUVs( node, currMaxMtl, mesh, i, numChannels, |
|
1, uvs1, uvs2, uvs3 ); |
|
if( flipOrder ) |
|
{ |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
temp = uvs1[ j ]; |
|
uvs1[ j ] = uvs3[ j ]; |
|
uvs3[ j ] = temp; |
|
} |
|
} |
|
} |
|
|
|
// Handle colors |
|
if( maxColorFace == nil ) |
|
{ |
|
colors[2] = colors[1] = colors[0] = white; |
|
} |
|
else |
|
{ |
|
colors[ 0 ] = colorArray[ maxColorFace->t[ flipOrder ? 2 : 0 ] ]; |
|
colors[ 1 ] = colorArray[ maxColorFace->t[ flipOrder ? 1 : 1 ] ]; |
|
colors[ 2 ] = colorArray[ maxColorFace->t[ flipOrder ? 0 : 2 ] ]; |
|
} |
|
|
|
// Don't want to write illum values to the vertex for composite materials |
|
if (illumArray == nil || includesComp) |
|
{ |
|
illums[ 0 ] = illums[ 1 ] = illums[ 2 ] = black; |
|
} |
|
else |
|
{ |
|
// MF_HORSE CARNAGE |
|
TVFace* tvFace = &mesh->mapFaces(MAP_SHADING)[i]; |
|
for( j = 0; j < 3; j++ ) |
|
illums[j] = illumArray[ tvFace->getTVert(flipOrder ? 2 - j : j) ]; |
|
// MF_HORSE CARNAGE |
|
} |
|
|
|
if (alphaMap != nil) // if it IS nil, then alpha values are all at the default 1.0 |
|
{ |
|
// MF_HORSE CARNAGE |
|
TVFace* tvFace = &mesh->mapFaces(MAP_ALPHA)[i]; |
|
for (j = 0; j < 3; j++) |
|
colors[j].a = alphaMap[ tvFace->getTVert(flipOrder ? 2 - j : j) ].x; |
|
// MF_HORSE CARNAGE |
|
} |
|
|
|
if (isComposite && !makeAlphaLayer) |
|
{ |
|
int index = ((plCompositeMtl *)currMaxMtl)->CanWriteAlpha(); |
|
int j; |
|
TVFace* tvFaces = mesh->mapFaces(MAP_SHADING); |
|
for (j = 0; j < 3; j++) |
|
{ |
|
switch(index) |
|
{ |
|
case plCompositeMtl::kCompBlendVertexAlpha: |
|
break; |
|
case plCompositeMtl::kCompBlendVertexIllumRed: |
|
colors[j].a = (tvFaces != nil ? illumMap[tvFaces[i].getTVert(flipOrder ? 2 - j : j)].x : 1.0f); |
|
break; |
|
case plCompositeMtl::kCompBlendVertexIllumGreen: |
|
colors[j].a = (tvFaces != nil ? illumMap[tvFaces[i].getTVert(flipOrder ? 2 - j : j)].y : 1.0f); |
|
break; |
|
case plCompositeMtl::kCompBlendVertexIllumBlue: |
|
colors[j].a = (tvFaces != nil ? illumMap[tvFaces[i].getTVert(flipOrder ? 2 - j : j)].z : 1.0f); |
|
break; |
|
default: // Different channels, thus we flush the alpha to 100 and do alpha through a 2nd layer. |
|
colors[j].a = 1.0f; |
|
break; |
|
} |
|
} |
|
} |
|
// Calculate normal for face |
|
if( node->HasNormalChan() ) |
|
{ |
|
// Someone has stuffed a requested normal into a map channel. |
|
// Ignore common sense and use it as is. |
|
int normChan = node->GetNormalChan(); |
|
TVFace* mapFaces = mesh->mapFaces(normChan); |
|
if( mapFaces ) |
|
{ |
|
TVFace* normFace = mapFaces + i; |
|
int ii; |
|
for( ii = 0; ii < 3; ii++ ) |
|
{ |
|
Point3 norm = mesh->mapVerts(normChan)[normFace->getTVert(ii)]; |
|
normals[ii].Set(norm.x, norm.y, norm.z); |
|
} |
|
} |
|
else |
|
{ |
|
if( fErrorMsg->Set(fWarnBadNormals, node->GetName(), fWarnBadNormalsMsg).CheckAskOrCancel() ) |
|
fWarnBadNormals = false; |
|
fErrorMsg->Set( false ); |
|
normals[0].Set(0,0,1.f); |
|
normals[1] = normals[2] = normals[0]; |
|
} |
|
|
|
} |
|
else if( node->GetRadiateNorms() ) |
|
{ |
|
int ii; |
|
for( ii = 0; ii < 3; ii++ ) |
|
{ |
|
Point3 pos = mesh->getVert(vertIdx[ii]) * otm; |
|
pos = pos * invOtm; |
|
|
|
normals[ii].Set(pos.x, pos.y, pos.z); |
|
} |
|
} |
|
else |
|
{ |
|
smGroup = smoothAll ? 1 : maxFace->getSmGroup(); |
|
if( smGroup == 0 ) |
|
{ |
|
hsVector3 v1, v2; |
|
v1.Set( &pos[ 1 ], &pos[ 0 ] ); |
|
v2.Set( &pos[ 2 ], &pos[ 1 ] ); // Hey, MAX does it...see normalCache building above |
|
// Note: if flipOrder is set, we have to reverse the order of the cross product, since |
|
// we already flipped the order of the points, to match what MAX would get |
|
normals[ 0 ] = normals[ 1 ] = normals[ 2 ] = flipOrder ? ( v2 % v1 ) : ( v1 % v2 ); |
|
} |
|
else |
|
{ |
|
normals[ 0 ] = vertNormalCache[ vertIdx[ 0 ] ].GetPlNormal( smGroup ); |
|
normals[ 1 ] = vertNormalCache[ vertIdx[ 1 ] ].GetPlNormal( smGroup ); |
|
normals[ 2 ] = vertNormalCache[ vertIdx[ 2 ] ].GetPlNormal( smGroup ); |
|
} |
|
} |
|
normals[ 0 ] = vertInvTransMatrix * normals[ 0 ]; |
|
normals[ 1 ] = vertInvTransMatrix * normals[ 1 ]; |
|
normals[ 2 ] = vertInvTransMatrix * normals[ 2 ]; |
|
|
|
// Adding normalization here, because we're going to compare them when searching for |
|
// this vertex to share. mf. |
|
normals[0].Normalize(); |
|
normals[1].Normalize(); |
|
normals[2].Normalize(); |
|
|
|
// The above section of code has just set any bump uv channels incorrectly, |
|
// but at least they are there. Now we just need to correct the values. |
|
if( bumpLayIdx[mainMatIndex] >= 0 ) |
|
{ |
|
TVFace* tvFace = mesh->mapFaces(bumpLayChan[mainMatIndex]+1) + i; |
|
ISetBumpUvs(bumpDuChan[mainMatIndex], vertDPosDuCache[mainMatIndex], tvFace, smGroup, uvs1, uvs2, uvs3); |
|
ISetBumpUvs(bumpDvChan[mainMatIndex], vertDPosDvCache[mainMatIndex], tvFace, smGroup, uvs1, uvs2, uvs3); |
|
} |
|
|
|
// Do this here, cause if we do it before we calculate the normals on smoothing group #0, |
|
// the normals will be wrong |
|
for( j = 0; j < 3; j++ ) |
|
pos[ j ] = vert2LMatrix * pos[ j ]; |
|
|
|
/* We already compute the index, this looks like redundant code - 7/26/01 Bob |
|
// Get span index |
|
if( isMultiMat ) |
|
{ |
|
mainMatIndex = maxFace->getMatID(); |
|
if( mainMatIndex >= numMaterials ) |
|
mainMatIndex = 0; |
|
} |
|
*/ |
|
if (isComposite) |
|
{ |
|
// I don't care about flipOrder here... it doesn't affect the index |
|
float opac[][2] = {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}; |
|
|
|
opac[0][0] = uvs1[numChannels].fX; |
|
opac[1][0] = uvs2[numChannels].fX; |
|
opac[2][0] = uvs3[numChannels].fX; |
|
opac[0][1] = uvs1[numChannels].fY; |
|
opac[1][1] = uvs2[numChannels].fY; |
|
opac[2][1] = uvs3[numChannels].fY; |
|
|
|
subMatIndex = ((plCompositeMtl *)currMaxMtl)->ComputeMaterialIndex(opac, 3) - 1; |
|
} |
|
// Add! |
|
hsAssert(ourAccumulators[mainMatIndex] != nil, "Trying to add a face with an unused sub-material."); |
|
ourAccumulators[ mainMatIndex ]->Get(subMatIndex)->AddVertex( vertIdx[ 0 ], &pos[ 0 ], &normals[ 0 ], colors[ 0 ], illums[ 0 ], uvs1 ); |
|
ourAccumulators[ mainMatIndex ]->Get(subMatIndex)->AddVertex( vertIdx[ 1 ], &pos[ 1 ], &normals[ 1 ], colors[ 1 ], illums[ 1 ], uvs2 ); |
|
ourAccumulators[ mainMatIndex ]->Get(subMatIndex)->AddVertex( vertIdx[ 2 ], &pos[ 2 ], &normals[ 2 ], colors[ 2 ], illums[ 2 ], uvs3 ); |
|
|
|
} |
|
|
|
|
|
/// Now go through each accumulator, take any data created and stuff it into a new span |
|
for( i = 0; i < numMaterials; i++ ) |
|
{ |
|
hsTArray<plExportMaterialData> *subMats = ourMaterials[i]; |
|
// A sub material of a MultiMat that never gets used will have a nil value, signifying no spans to export |
|
if (subMats == nil) |
|
continue; |
|
for( j = 0; j < subMats->GetCount(); j++) |
|
{ |
|
plMAXVertexAccumulator *accum = ourAccumulators[i]->Get(j); |
|
|
|
// With composite materials, not every accumulator will have faces. Only create spans for the ones that do. |
|
if (accum->GetVertexCount() == 0) |
|
continue; |
|
|
|
plGeometrySpan *span = new plGeometrySpan; |
|
|
|
span->BeginCreate( subMats->Get(j).fMaterial, l2wMatrix, ourFormat ); |
|
span->fLocalToOBB = node->GetLocalToOBB44(); |
|
span->fOBBToLocal = node->GetOBBToLocal44(); |
|
|
|
accum->StuffMyData( node, span, mesh, skinData ); |
|
span->fProps |= sharedSpanProps; |
|
|
|
span->fWaterHeight = waterHeight; |
|
|
|
if( (bumpDuChan[i] >= 0) && (bumpDvChan[i] > 0) ) |
|
span->fLocalUVWChans = (bumpDuChan[i] << 8) | bumpDvChan[i]; |
|
|
|
if( (span->fMaterial != nil) |
|
&& (span->fMaterial->GetNumLayers() > 0) |
|
&& (span->fMaterial->GetLayer( 0 )->GetState().fBlendFlags & hsGMatState::kBlendMask) ) |
|
{ |
|
span->fProps |= plGeometrySpan::kRequiresBlending; |
|
} |
|
if( node->GetForceSortable() ) |
|
span->fProps |= plGeometrySpan::kRequiresBlending; |
|
|
|
span->EndCreate(); |
|
|
|
float minDist, maxDist; |
|
if( hsMaterialConverter::HasVisDists(node, i, minDist, maxDist) ) |
|
{ |
|
span->fMinDist = (minDist); |
|
span->fMaxDist = (maxDist); |
|
} |
|
|
|
// If we're not doing preshading later, make sure everything is illuminated so you can see |
|
if (doPreshading) |
|
{ |
|
hsColorRGBA gray; |
|
gray.Set(0.5, 0.5, 0.5, 0.0); |
|
for( int iVert = 0; iVert < span->fNumVerts; iVert++ ) |
|
span->StuffVertex( iVert, &white, &gray ); |
|
} |
|
|
|
if( span->fNumVerts > 0 ) |
|
spanArray.Append( span ); |
|
else |
|
delete span; |
|
} |
|
} |
|
|
|
void SetWaterColor(hsTArray<plGeometrySpan*>& spans); |
|
|
|
// A bit of test hack here. Remind me to nuke it. |
|
if( node->GetCalcEdgeLens() || node->UserPropExists("XXXWaterColor") ) |
|
SetWaterColor(spanArray); |
|
|
|
|
|
// Now that we have our nice spans, see if they need to be diced up a bit |
|
int maxFaces, minFaces; |
|
float maxSize; |
|
if( node->GetGeoDice(maxFaces, maxSize, minFaces) ) |
|
{ |
|
plGeoSpanDice dice; |
|
dice.SetMaxFaces(maxFaces); |
|
dice.SetMaxSize(hsPoint3(maxSize, maxSize, maxSize)); |
|
dice.SetMinFaces(minFaces); |
|
dice.Dice(spanArray); |
|
} |
|
|
|
/// Check for overflow (only do it if our initial tests showed there could be overflow; this |
|
/// is so these tests don't slow down the loop unless absolutely needed). |
|
// We're going to go ahead and quietly break up the mesh if it needs breaking, |
|
// because we can probably do a better job of it than production anyway. |
|
if( checkForOverflow ) |
|
{ |
|
bool needMoreDicing = false; |
|
int i; |
|
for( i = 0; i < spanArray.GetCount(); i++ ) |
|
{ |
|
plAccessGeometry accGeom; |
|
plAccessSpan accSpan; |
|
accGeom.AccessSpanFromGeometrySpan(accSpan, spanArray[i]); |
|
bool destroySpan = false; |
|
if( accSpan.HasAccessVtx() ) |
|
{ |
|
if( accSpan.AccessVtx().VertCount() >= plGBufferGroup::kMaxNumVertsPerBuffer ) |
|
{ |
|
needMoreDicing = true; |
|
} |
|
} |
|
if( accSpan.HasAccessTri() ) |
|
{ |
|
if( accSpan.AccessTri().TriCount() * 3 >= plGBufferGroup::kMaxNumIndicesPerBuffer ) |
|
{ |
|
needMoreDicing = true; |
|
} |
|
} |
|
accGeom.Close(accSpan); |
|
} |
|
if( needMoreDicing ) |
|
{ |
|
// Could just dice the ones that need it, but whatever. mf. |
|
plConst(int) kAutoMaxFaces(5000); |
|
plConst(float) kAutoMaxSize(10000.f); |
|
plConst(int) kAutoMinFaces(1000); |
|
plGeoSpanDice dice; |
|
dice.SetMaxFaces(kAutoMaxFaces); |
|
dice.SetMaxSize(hsPoint3(kAutoMaxSize,kAutoMaxSize,kAutoMaxSize)); |
|
dice.SetMinFaces(kAutoMinFaces); |
|
dice.Dice(spanArray); |
|
} |
|
} |
|
throw (bool)true; |
|
} |
|
catch( bool retVal ) |
|
{ |
|
/// Cleanup! |
|
for( i = 0; i < vertNormalCache.GetCount(); i++ ) |
|
vertNormalCache[ i ].DestroyChain(); |
|
|
|
for( i = 0; i < numMaterials; i++ ) |
|
{ |
|
if( vertDPosDuCache != nil ) |
|
{ |
|
for( j = 0; j < vertDPosDuCache[i].GetCount(); j++ ) |
|
vertDPosDuCache[i][j].DestroyChain(); |
|
} |
|
if( vertDPosDvCache != nil ) |
|
{ |
|
for( j = 0; j < vertDPosDvCache[i].GetCount(); j++ ) |
|
vertDPosDvCache[i][j].DestroyChain(); |
|
} |
|
|
|
if (ourAccumulators[i] == nil) |
|
continue; |
|
for( j = 0; j < ourAccumulators[ i ]->GetCount(); j++ ) |
|
{ |
|
delete ourAccumulators[ i ]->Get(j); |
|
} |
|
delete ourMaterials[ i ]; |
|
delete ourAccumulators[ i ]; |
|
} |
|
delete [] vertDPosDuCache; |
|
delete [] vertDPosDvCache; |
|
delete [] colorArray; |
|
delete [] illumArray; |
|
|
|
IDeleteTempGeometry(); |
|
|
|
return retVal; |
|
} |
|
|
|
return true; |
|
hsGuardEnd; |
|
} |
|
|
|
//// ICreateHexColor ///////////////////////////////////////////////////////// |
|
|
|
uint32_t plMeshConverter::ICreateHexColor( float r, float g, float b ) |
|
{ |
|
uint32_t ru, gu, bu, au; |
|
|
|
|
|
au = 0xff000000; |
|
ru = (uint32_t)(r * 255.0f); |
|
gu = (uint32_t)(g * 255.0f); |
|
bu = (uint32_t)(b * 255.0f); |
|
return au | ( ru << 16 ) | ( gu << 8 ) | ( bu ); |
|
} |
|
|
|
uint32_t plMeshConverter::ISetHexAlpha( uint32_t color, float alpha) |
|
{ |
|
uint32_t alphaBits = alpha * 255; |
|
alphaBits <<= 24; |
|
return color & 0x00ffffff | alphaBits; |
|
} |
|
|
|
// Delete temp geo stuff allocated (either directly or indirectly) |
|
// via IGetNodeMesh(). |
|
void plMeshConverter::IDeleteTempGeometry() |
|
{ |
|
if( fTriObjToDelete != nil ) |
|
{ |
|
fTriObjToDelete->DeleteMe(); |
|
fTriObjToDelete = nil; |
|
} |
|
if( fMeshToDelete ) |
|
{ |
|
delete fMeshToDelete; |
|
fMeshToDelete = nil; |
|
} |
|
} |
|
|
|
//// IGetNodeMesh //////////////////////////////////////////////////////////// |
|
// Get the Mesh object attached to a node. Returns nil if the node |
|
// is not a triMesh object |
|
|
|
Mesh *plMeshConverter::IGetNodeMesh( plMaxNode *node ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::IGetNodeMesh" ); |
|
|
|
const char* dbgNodeName = node->GetName(); |
|
|
|
fTriObjToDelete = nil; |
|
fMeshToDelete = nil; |
|
|
|
// Get da object |
|
Object *obj = node->EvalWorldState( fConverterUtils.GetTime( fInterface ) ).obj; |
|
if( obj == nil ) |
|
return nil; |
|
|
|
if( !obj->CanConvertToType( triObjectClassID ) ) |
|
return nil; |
|
|
|
// Convert to triMesh object |
|
TriObject *meshObj = (TriObject *)obj->ConvertToType( fConverterUtils.GetTime( fInterface ), triObjectClassID ); |
|
if( meshObj == nil ) |
|
return nil; |
|
|
|
if( meshObj != obj ) |
|
fTriObjToDelete = meshObj; |
|
|
|
// Get the mesh |
|
Mesh *mesh = &(meshObj->mesh); |
|
if( mesh->getNumFaces() == 0 ) |
|
return nil; |
|
|
|
if( node->GetDup2Sided() ) |
|
{ |
|
mesh = IDuplicate2Sided(node, mesh); |
|
|
|
IDeleteTempGeometry(); |
|
|
|
fMeshToDelete = mesh; |
|
} |
|
|
|
return mesh; |
|
hsGuardEnd; |
|
} |
|
|
|
Mesh* plMeshConverter::IDuplicate2Sided(plMaxNode* node, Mesh* mesh) |
|
{ |
|
mesh = new Mesh(*mesh); |
|
|
|
Mtl* mtl = node->GetMtl(); |
|
|
|
BitArray faces(mesh->getNumFaces()); |
|
|
|
int num2Sided = 0; |
|
|
|
int origNumFaces = mesh->getNumFaces(); |
|
|
|
int i; |
|
for( i = 0; i < mesh->getNumFaces(); i++ ) |
|
{ |
|
if( hsMaterialConverter::IsTwoSided(mtl, mesh->faces[i].getMatID()) ) |
|
{ |
|
num2Sided++; |
|
faces.Set(i); |
|
} |
|
} |
|
|
|
if( !num2Sided ) |
|
return mesh; |
|
|
|
MeshDelta meshDelta(*mesh); |
|
meshDelta.CloneFaces(*mesh, faces); |
|
meshDelta.Apply(*mesh); |
|
|
|
BitArray verts(mesh->getNumVerts()); |
|
verts.SetAll(); |
|
const float kWeldThresh = 0.1f; |
|
meshDelta.WeldByThreshold(*mesh, verts, kWeldThresh); |
|
meshDelta.Apply(*mesh); |
|
|
|
hsAssert(origNumFaces + num2Sided == mesh->getNumFaces(), "Whoa, lost or gained, unexpected"); |
|
|
|
for( i = origNumFaces; i < mesh->getNumFaces(); i++ ) |
|
{ |
|
meshDelta.FlipNormal(*mesh, i); |
|
} |
|
meshDelta.Apply(*mesh); |
|
|
|
return mesh; |
|
} |
|
|
|
//// IGenerateUVs //////////////////////////////////////////////////////////// |
|
// Generates the UV coordinates for the three vertices of a given face. |
|
// Returns the number of UV channels. Ripped off of SetUVs() from the old |
|
// hsMeshConverter. |
|
|
|
int plMeshConverter::IGenerateUVs( plMaxNode *node, Mtl *maxMtl, Mesh *mesh, int faceIdx, int numChan, int numBlend, |
|
hsPoint3 *uvs1, hsPoint3 *uvs2, hsPoint3 *uvs3 ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::IGenerateUVs" ); |
|
|
|
if( !( maxMtl && ( hsMaterialConverter::Instance().IsMultiMat( maxMtl ) || ( maxMtl->Requirements(-1) & MTLREQ_UV ) ) ) ) |
|
{ |
|
return 0; |
|
} |
|
|
|
|
|
// To avoid transforming the shared UVs while rendering, we will |
|
// sometimes pretransform them and not share. |
|
|
|
int j, k; |
|
Face *face = &mesh->faces[ faceIdx ]; |
|
|
|
|
|
|
|
if( hsMaterialConverter::Instance().IsMultiMat( maxMtl ) ) |
|
{ |
|
int faceMtlIndex = face->getMatID(); |
|
if( faceMtlIndex >= maxMtl->NumSubMtls() ) // we'll warn in createtrimeshrecur |
|
faceMtlIndex = 0; |
|
} |
|
|
|
bool firstWarn = true; |
|
hsPoint3 pt; |
|
|
|
/// Loop through the vertices |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
int chan; |
|
for( chan = 0; chan < numChan; chan++ ) |
|
{ |
|
if( mesh->mapFaces( chan + 1 ) ) |
|
{ |
|
TVFace* tvFace = &mesh->mapFaces( chan + 1 )[ faceIdx ]; |
|
UVVert* tVerts = mesh->mapVerts( chan + 1 ); |
|
|
|
if( firstWarn && fErrorMsg->Set( !tvFace, node->GetName(), "Check mapping on textured objects" ).CheckAndAsk() ) |
|
{ |
|
firstWarn = false; |
|
} |
|
fErrorMsg->Set( false ); |
|
if( !tvFace ) |
|
{ |
|
continue; |
|
} |
|
|
|
int tvIdx = tvFace->getTVert( j ); |
|
|
|
if( tvIdx >= mesh->getNumMapVerts( chan + 1 ) ) |
|
{ |
|
static int muteWarn = false; |
|
if( !muteWarn ) |
|
{ |
|
muteWarn = fErrorMsg->Set( true, node->GetName(), "Check mapping on channel %d!!!", chan + 1 ).CheckAskOrCancel(); |
|
fErrorMsg->Set( false ); |
|
} |
|
tvIdx = 0; |
|
} |
|
|
|
UVVert uv = tVerts[tvIdx]; |
|
|
|
// The artists set the 3rd coordinate to help create the mapping, |
|
// but we never need it at runtime, so let's set it to zero on |
|
// export and then the vert coder can detect that it doesn't |
|
// even need to write it. |
|
pt.Set( uv.x, 1.0f - uv.y, 0.f ); |
|
|
|
if( _isnan( (double)pt.fX ) || _isnan( (double)pt.fY ) || _isnan( (double)pt.fZ ) ) |
|
pt.Set( 0, 0, 0 ); |
|
|
|
switch( j ) |
|
{ |
|
case 0: uvs1[ chan ] = pt; break; |
|
case 1: uvs2[ chan ] = pt; break; |
|
case 2: uvs3[ chan ] = pt; break; |
|
} |
|
} |
|
|
|
} |
|
|
|
// That takes care of the UVs MAX gives us. Do we need some leftover channels to store our blending info? |
|
for (k = numChan; k < numChan + numBlend; k++) |
|
{ |
|
UVVert *alphas = mesh->mapVerts(MAP_ALPHA); |
|
UVVert *illums = mesh->mapVerts(MAP_SHADING); |
|
UVVert pt; |
|
pt.z = 0.0f; |
|
|
|
TVFace* alphaFace = mesh->mapFaces(MAP_ALPHA) ? &mesh->mapFaces(MAP_ALPHA)[faceIdx] : nil; |
|
TVFace* illumFace = mesh->mapFaces(MAP_SHADING) ? &mesh->mapFaces(MAP_SHADING)[faceIdx] : nil; |
|
|
|
if (hsMaterialConverter::Instance().IsCompositeMat( maxMtl )) |
|
{ |
|
plCompositeMtl *compMtl = (plCompositeMtl *)maxMtl; |
|
IParamBlock2 *pb = maxMtl->GetParamBlockByID(kCompPasses); |
|
|
|
// MF_HORSE CARNAGE |
|
|
|
|
|
compMtl->SetOpacityVal(&pt.x, |
|
(alphas == nil |
|
? nil |
|
: &alphas[ alphaFace->getTVert(j) ]), |
|
(illums == nil |
|
? nil |
|
: &illums[ illumFace->getTVert(j) ]), |
|
pb->GetInt(kCompBlend, 0, 0)); |
|
|
|
compMtl->SetOpacityVal(&pt.y, |
|
(alphas == nil |
|
? nil |
|
: &alphas[ alphaFace->getTVert(j) ]), |
|
(illums == nil |
|
? nil |
|
: &illums[ illumFace->getTVert(j) ]), |
|
pb->GetInt(kCompBlend, 0, 1)); |
|
// MF_HORSE CARNAGE |
|
} |
|
else // all other materials |
|
{ |
|
pt.y = 0.0; |
|
pt.z = 1.0; |
|
if (alphas == nil || alphaFace == nil) |
|
pt.x = 1.0; |
|
else |
|
pt.x = alphas[alphaFace->getTVert(j)].x; |
|
} |
|
|
|
switch( j ) |
|
{ |
|
case 0: uvs1[ k ].fX = pt.x; uvs1[ k ].fY = pt.y; uvs1[ k ].fZ = pt.z; break; |
|
case 1: uvs2[ k ].fX = pt.x; uvs2[ k ].fY = pt.y; uvs2[ k ].fZ = pt.z; break; |
|
case 2: uvs3[ k ].fX = pt.x; uvs3[ k ].fY = pt.y; uvs3[ k ].fZ = pt.z; break; |
|
} |
|
} |
|
} |
|
|
|
return numChan; |
|
hsGuardEnd; |
|
} |
|
|
|
void plMeshConverter::ISetWaterDecEnvUvSrcs(hsTArray<hsTArray<plExportMaterialData> *>& ourMaterials, |
|
hsTArray<int16_t>& bumpLayIdx, |
|
hsTArray<int16_t>& bumpLayChan, |
|
hsTArray<int16_t>& bumpDuChan, |
|
hsTArray<int16_t>& bumpDvChan) |
|
{ |
|
bumpLayIdx.SetCount(ourMaterials.GetCount()); |
|
bumpLayChan.SetCount(ourMaterials.GetCount()); |
|
bumpDuChan.SetCount(ourMaterials.GetCount()); |
|
bumpDvChan.SetCount(ourMaterials.GetCount()); |
|
|
|
int i; |
|
for( i = 0; i < ourMaterials.GetCount(); i++ ) |
|
{ |
|
bumpLayIdx[i] = i; |
|
bumpLayChan[i] = 0; |
|
bumpDuChan[i] = 1; |
|
bumpDvChan[i] = 2; |
|
} |
|
} |
|
|
|
void plMeshConverter::ISetBumpUvSrcs(hsTArray<hsTArray<plExportMaterialData> *>& ourMaterials, |
|
hsTArray<int16_t>& bumpLayIdx, |
|
hsTArray<int16_t>& bumpLayChan, |
|
hsTArray<int16_t>& bumpDuChan, |
|
hsTArray<int16_t>& bumpDvChan) |
|
{ |
|
bumpLayIdx.SetCount(ourMaterials.GetCount()); |
|
bumpLayChan.SetCount(ourMaterials.GetCount()); |
|
bumpDuChan.SetCount(ourMaterials.GetCount()); |
|
bumpDvChan.SetCount(ourMaterials.GetCount()); |
|
|
|
int i; |
|
for( i = 0; i < ourMaterials.GetCount(); i++ ) |
|
{ |
|
bumpLayIdx[i] = -1; |
|
bumpLayChan[i] = -1; |
|
bumpDuChan[i] = -1; |
|
bumpDvChan[i] = -1; |
|
|
|
// The following two lines pretty much rule out composites with bump maps. |
|
if( !ourMaterials[i] ) |
|
continue; |
|
|
|
if( ourMaterials[i]->GetCount() != 1 ) |
|
continue; |
|
|
|
|
|
hsGMaterial* ourMat = ourMaterials[i]->Get(0).fMaterial; |
|
int j; |
|
for( j = 0; j < ourMat->GetNumLayers(); j++ ) |
|
{ |
|
if( ourMat->GetLayer(j)->GetMiscFlags() & hsGMatState::kMiscBumpLayer ) |
|
{ |
|
bumpLayIdx[i] = j; |
|
bumpLayChan[i] = ourMat->GetLayer(j)->GetUVWSrc(); |
|
} |
|
|
|
if( ourMat->GetLayer(j)->GetMiscFlags() & hsGMatState::kMiscBumpDu ) |
|
bumpDuChan[i] = ourMat->GetLayer(j)->GetUVWSrc(); |
|
|
|
if( ourMat->GetLayer(j)->GetMiscFlags() & hsGMatState::kMiscBumpDv ) |
|
bumpDvChan[i] = ourMat->GetLayer(j)->GetUVWSrc(); |
|
} |
|
} |
|
} |
|
|
|
void plMeshConverter::ISetBumpUvs(int16_t uvChan, hsTArray<plMAXVertNormal>& vertDPosDuvCache, TVFace* tvFace, uint32_t smGroup, |
|
hsPoint3* uvs1, hsPoint3* uvs2, hsPoint3* uvs3) |
|
{ |
|
if( uvChan < 0 ) |
|
return; |
|
|
|
uvs1[uvChan] = *(hsPoint3*)&vertDPosDuvCache[tvFace->getTVert(0)].GetPlNormal(smGroup); |
|
uvs2[uvChan] = *(hsPoint3*)&vertDPosDuvCache[tvFace->getTVert(1)].GetPlNormal(smGroup); |
|
uvs3[uvChan] = *(hsPoint3*)&vertDPosDuvCache[tvFace->getTVert(2)].GetPlNormal(smGroup); |
|
} |
|
|
|
// Determine if we're going to need a uv gradient channel. |
|
// If we do need one, determine which uvw channel needs the gradient. |
|
// Finally, make the gradients, smoothing according to smooth groups (just like vertex normals). |
|
// |
|
// If we decided we needed them, they are in the output arrays, otherwise the output arrays are made empty. |
|
void plMeshConverter::ISmoothUVGradients(plMaxNode* node, Mesh* mesh, |
|
hsTArray<hsTArray<plExportMaterialData> *>& ourMaterials, |
|
hsTArray<int16_t>& bumpLayIdx, hsTArray<int16_t>& bumpLayChan, |
|
hsTArray<plMAXVertNormal>* vertDPosDuCache, hsTArray<plMAXVertNormal>* vertDPosDvCache) |
|
{ |
|
const char* dbgNodeName = node->GetName(); |
|
|
|
Mtl* mainMtl = hsMaterialConverter::Instance().GetBaseMtl( node ); |
|
|
|
bool needsGradientUvs = hsMaterialConverter::Instance().HasBumpLayer(node, mainMtl) || node->GetWaterDecEnv(); |
|
|
|
if( needsGradientUvs ) |
|
{ |
|
int matIdx; |
|
for( matIdx = 0; matIdx < ourMaterials.GetCount(); matIdx++ ) |
|
{ |
|
if( bumpLayIdx[matIdx] >= 0 ) |
|
{ |
|
uint32_t uvwSrc = bumpLayChan[matIdx]; |
|
if( mesh->getNumMapVerts(uvwSrc+1) && mesh->mapVerts(uvwSrc+1) ) |
|
{ |
|
vertDPosDuCache[matIdx].SetCount(mesh->getNumMapVerts(uvwSrc+1)); |
|
vertDPosDvCache[matIdx].SetCount(mesh->getNumMapVerts(uvwSrc+1)); |
|
} |
|
else |
|
{ |
|
// Ooops. This is probably an error somewhere. |
|
hsAssert(false, "Thought we had a valid bump map, but we don't."); |
|
bumpLayIdx[matIdx] = -1; |
|
} |
|
} |
|
} |
|
|
|
bool isMultiMat = hsMaterialConverter::Instance().IsMultiMat(mainMtl); |
|
int i; |
|
for( i = 0; i < mesh->getNumFaces(); i++ ) |
|
{ |
|
const plLayerInterface* layer = nil; |
|
if( isMultiMat ) |
|
{ |
|
int index = mesh->faces[i].getMatID(); |
|
if (index >= mainMtl->NumSubMtls()) |
|
index = 0; |
|
|
|
matIdx = index; |
|
if( bumpLayIdx[index] >= 0 ) |
|
layer = ourMaterials[index]->Get(0).fMaterial->GetLayer(bumpLayIdx[index]); |
|
} |
|
else |
|
{ |
|
matIdx = 0; |
|
if( bumpLayIdx[0] >= 0 ) |
|
layer = ourMaterials[0]->Get(0).fMaterial->GetLayer(bumpLayIdx[0]); |
|
} |
|
if( layer ) |
|
{ |
|
Point3 dPosDu = IGetUvGradient(node, layer->GetTransform(), layer->GetUVWSrc(), |
|
mesh, i, |
|
0); |
|
Point3 dPosDv = IGetUvGradient(node, layer->GetTransform(), layer->GetUVWSrc(), |
|
mesh, i, |
|
1); |
|
|
|
// #define MF_BUMP_CHECK_DUXDV |
|
#ifdef MF_BUMP_CHECK_DUXDV |
|
Point3 duXdv = ::Normalize(dPosDu) ^ ::Normalize(dPosDv); |
|
|
|
Point3 v0 = mesh->verts[ mesh->faces[i].v[ 0 ] ]; |
|
Point3 v1 = mesh->verts[ mesh->faces[i].v[ 1 ] ]; |
|
Point3 v2 = mesh->verts[ mesh->faces[i].v[ 2 ] ]; |
|
|
|
Point3 norm = ::Normalize(( v1 - v0 ) ^ ( v2 - v1 )); |
|
|
|
Point3 diff = duXdv - norm; |
|
|
|
static int doAgain = false; |
|
if( doAgain ) |
|
{ |
|
dPosDu = IGetUvGradient(node, layer->GetTransform(), layer->GetUVWSrc(), |
|
mesh, i, |
|
0); |
|
dPosDv = IGetUvGradient(node, layer->GetTransform(), layer->GetUVWSrc(), |
|
mesh, i, |
|
1); |
|
} |
|
#endif // MF_BUMP_CHECK_DUXDV |
|
|
|
if( node->GetWaterDecEnv() ) |
|
{ |
|
dPosDu.z = dPosDv.z = 0.f; |
|
} |
|
|
|
// Flip the direction of dPosDv, because we flip textures about V for histerical reasons. |
|
dPosDv = -dPosDv; |
|
|
|
TVFace* tvFace = &mesh->mapFaces(layer->GetUVWSrc() + 1)[i]; |
|
int j; |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
vertDPosDuCache[matIdx][tvFace->getTVert(j)].AddNormal(dPosDu, mesh->faces[i].smGroup); |
|
vertDPosDvCache[matIdx][tvFace->getTVert(j)].AddNormal(dPosDv, mesh->faces[i].smGroup); |
|
} |
|
} |
|
} |
|
for( matIdx = 0; matIdx < ourMaterials.GetCount(); matIdx++ ) |
|
{ |
|
for( i = 0; i < vertDPosDuCache[matIdx].GetCount(); i++ ) |
|
{ |
|
vertDPosDuCache[matIdx][i].Normalize(); |
|
vertDPosDvCache[matIdx][i].Normalize(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
// Get dPos/du into uvws[0], and dPos/dv into uvws[1]. dPos should be in the object's local space. |
|
Point3 plMeshConverter::IGetUvGradient(plMaxNode* node, |
|
const hsMatrix44& uvXform44, int16_t bmpUvwSrc, // Transform and uvwSrc of layer to gradient |
|
Mesh *mesh, int faceIdx, |
|
int iUV) // 0 = uvw.x, 1 = uv2.y |
|
{ |
|
Point3 uvwOut(0,0,0); |
|
if( bmpUvwSrc < 0 ) |
|
return uvwOut; // Not Error. |
|
|
|
if( bmpUvwSrc >= mesh->getNumMaps() ) |
|
return uvwOut; // Error? |
|
|
|
TVFace* tvFace = &mesh->mapFaces(bmpUvwSrc + 1)[faceIdx]; |
|
UVVert* tVerts = mesh->mapVerts(bmpUvwSrc + 1); |
|
if( !tvFace ) |
|
return uvwOut; // Error? |
|
if( !tVerts ) |
|
return uvwOut; // Error? |
|
|
|
Matrix3 v2l = node->GetVertToLocal(); |
|
|
|
bool flipOrder = v2l.Parity(); |
|
int vtxIdx = 0; |
|
int vtxNext = flipOrder ? 2 : 1; |
|
int vtxLast = flipOrder ? 1 : 2; |
|
|
|
// Get the three verts, v0-v2, where v0 is the corner in question. |
|
Face* face = &mesh->faces[faceIdx]; |
|
Point3 v0 = v2l * mesh->verts[face->getVert(vtxIdx)]; |
|
Point3 v1 = v2l * mesh->verts[face->getVert(vtxNext)]; |
|
Point3 v2 = v2l * mesh->verts[face->getVert(vtxLast)]; |
|
|
|
// Get the three uvs, uv0-uv2, matching above verts. |
|
if( tvFace->getTVert(vtxIdx) >= mesh->getNumMapVerts(bmpUvwSrc + 1) ) |
|
return uvwOut; // Error? |
|
if( tvFace->getTVert(vtxNext) >= mesh->getNumMapVerts(bmpUvwSrc + 1) ) |
|
return uvwOut; // Error? |
|
if( tvFace->getTVert(vtxLast) >= mesh->getNumMapVerts(bmpUvwSrc + 1) ) |
|
return uvwOut; // Error? |
|
|
|
Matrix3 uvwXform = plMaxNodeBase::Matrix44ToMatrix3(uvXform44); |
|
|
|
Point3 uv0 = uvwXform * tVerts[tvFace->getTVert(vtxIdx)]; |
|
Point3 uv1 = uvwXform * tVerts[tvFace->getTVert(vtxNext)]; |
|
Point3 uv2 = uvwXform * tVerts[tvFace->getTVert(vtxLast)]; |
|
|
|
|
|
const float kRealSmall = 1.e-6f; |
|
// First, look for degenerate cases. |
|
// If (uvn - uvm)[!iUV] == 0 |
|
// then (vn - vm) is tangent in iUV dimension |
|
// Just be careful about direction, since (vn-vm) may be opposite direction from |
|
// increasing iUV. |
|
int iNotUV = !iUV; |
|
float del = uv0[iNotUV] - uv1[iNotUV]; |
|
if( fabs(del) < kRealSmall ) |
|
{ |
|
if( uv0[iUV] - uv1[iUV] < 0 ) |
|
uvwOut = v1 - v0; |
|
else |
|
uvwOut = v0 - v1; |
|
return uvwOut; |
|
} |
|
del = uv2[iNotUV] - uv1[iNotUV]; |
|
if( fabs(del) < kRealSmall ) |
|
{ |
|
if( uv2[iUV] - uv1[iUV] < 0 ) |
|
uvwOut = v1 - v2; |
|
else |
|
uvwOut = v2 - v1; |
|
return uvwOut; |
|
} |
|
del = uv2[iNotUV] - uv0[iNotUV]; |
|
if( fabs(del) < kRealSmall ) |
|
{ |
|
if( uv2[iUV] - uv0[iUV] < 0 ) |
|
uvwOut = v0 - v2; |
|
else |
|
uvwOut = v2 - v0; |
|
return uvwOut; |
|
} |
|
|
|
// Okay, none of the edges are along the dU gradient. That's good, because |
|
// it means we don't have to worry about divides by zero in what we're about |
|
// to do. |
|
del = uv0[iNotUV] - uv1[iNotUV]; |
|
del = 1.f / del; |
|
Point3 v0Mv1 = v0 - v1; |
|
v0Mv1 *= del; |
|
float v0uv = (uv0[iUV] - uv1[iUV]) * del; |
|
|
|
del = uv2[iNotUV] - uv1[iNotUV]; |
|
del = 1.f / del; |
|
Point3 v2Mv1 = v2 - v1; |
|
v2Mv1 *= del; |
|
float v2uv = (uv2[iUV] - uv1[iUV]) * del; |
|
|
|
if( v0uv > v2uv ) |
|
uvwOut = v0Mv1 - v2Mv1; |
|
else |
|
uvwOut = v2Mv1 - v0Mv1; |
|
|
|
return uvwOut; |
|
} |
|
|
|
//// IGetUVTransform ///////////////////////////////////////////////////////// |
|
// Gets the UV transform matrix for the given channel. |
|
|
|
void plMeshConverter::IGetUVTransform( plMaxNode *node, Mtl *mtl, Matrix3 *uvTransform, int which ) |
|
{ |
|
hsGuardBegin( "plMeshConverter::IGetUVTransform" ); |
|
|
|
uvTransform->IdentityMatrix(); |
|
|
|
if( !mtl ) |
|
return; |
|
|
|
Texmap* texMap = hsMaterialConverter::Instance().GetUVChannelBase(node, mtl, which); |
|
|
|
if( !texMap ) |
|
return; |
|
|
|
BitmapTex *bitmapTex = (BitmapTex *)texMap; |
|
|
|
#ifndef NDEBUG |
|
CStr className; |
|
texMap->GetClassName(className); |
|
if( strcmp(className,"Bitmap") && strcmp(className,"Plasma Layer") && strcmp(className,"Plasma Layer Dbg.")) |
|
return; |
|
|
|
char txtFileName[256]; |
|
strcpy(txtFileName, bitmapTex->GetMapName()); |
|
#endif // NDEBUG |
|
|
|
StdUVGen *uvGen = bitmapTex->GetUVGen(); |
|
|
|
uvGen->GetUVTransform(*uvTransform); |
|
|
|
// We're going to munge the internals of the matrix here, but we won't need to worry about |
|
// the flags (ident etc.) because we're not changing the character of the matrix (unless |
|
// it's a pure translation by an integer amount, which is a no-op). |
|
// We also don't have to worry about preserving unit offsets for animation, because the |
|
// output of this function is used as a static matrix (for uv generation). We could check |
|
// and not do this on animated transforms or something. mf |
|
// Note that we can only do this if the texture wraps (not clamps) |
|
if( !hsMaterialConverter::Instance().PreserveUVOffset(mtl) ) |
|
{ |
|
MRow* data = uvTransform->GetAddr(); |
|
int i; |
|
for( i = 0; i < 2; i++ ) |
|
{ |
|
if( fabsf(data[3][i]) >= 1.f ) |
|
{ |
|
data[3][i] -= float(int(data[3][i])); |
|
} |
|
} |
|
} |
|
hsGuardEnd; |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////////////// |
|
//// Helper Class Functions ////////////////////////////////////////////////// |
|
////////////////////////////////////////////////////////////////////////////// |
|
|
|
//// plMAXVertexAccNode Constructor ////////////////////////////////////////// |
|
|
|
plMAXVertexAccNode::plMAXVertexAccNode( const hsPoint3 *point, const hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, int numChannels, const hsPoint3 *uvs, uint32_t index ) |
|
{ |
|
int i; |
|
|
|
|
|
fPoint = *point; |
|
fNormal = *normal; |
|
fColor = color; |
|
fIllum = illum; |
|
for( i = 0; i < numChannels; i++ ) |
|
fUVs[ i ] = uvs[ i ]; |
|
fNumChannels = numChannels; |
|
fIndex = index; |
|
|
|
fNext = nil; |
|
} |
|
|
|
//// IsEqual ///////////////////////////////////////////////////////////////// |
|
// Determines whether the node matches the values given. |
|
|
|
bool plMAXVertexAccNode::IsEqual( const hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, const hsPoint3 *uvs ) |
|
{ |
|
int i; |
|
|
|
|
|
if( color != fColor || !( *normal == fNormal ) || illum != fIllum ) |
|
return false; |
|
|
|
for( i = 0; i < fNumChannels; i++ ) |
|
{ |
|
if( !( uvs[ i ] == fUVs[ i ] ) ) |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
//// plMAXVertexAccumulator Constructor & Destructor ///////////////////////// |
|
|
|
plMAXVertexAccumulator::plMAXVertexAccumulator( int numOrigPoints, int numChannels ) |
|
{ |
|
fNumPoints = numOrigPoints; |
|
fNumChannels = numChannels; |
|
|
|
fPointList = new plMAXVertexAccNodePtr[ fNumPoints ]; |
|
memset( fPointList, 0, sizeof( plMAXVertexAccNode * ) * fNumPoints ); |
|
|
|
fIndices.Reset(); |
|
fInverseVertTable.Reset(); |
|
fNumVertices = 0; |
|
} |
|
|
|
plMAXVertexAccumulator::~plMAXVertexAccumulator() |
|
{ |
|
int i; |
|
plMAXVertexAccNode *node; |
|
|
|
|
|
for( i = 0; i < fNumPoints; i++ ) |
|
{ |
|
while( fPointList[ i ] != nil ) |
|
{ |
|
node = fPointList[ i ]->fNext; |
|
delete fPointList[ i ]; |
|
fPointList[ i ] = node; |
|
} |
|
} |
|
|
|
delete [] fPointList; |
|
} |
|
|
|
//// AddVertex /////////////////////////////////////////////////////////////// |
|
// Adds a vertex to this accumulator. If it already exists, adds the normals |
|
// together and stores the old index; if not, creates a new node and adds |
|
// an index for it. In the end, fIndices will be our final index buffer, |
|
// while fInverseVertTable is basically an array telling us where to get |
|
// our vertices when building the vertex buffer. |
|
|
|
void plMAXVertexAccumulator::AddVertex( int index, hsPoint3 *point, hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, hsPoint3 *uvs ) |
|
{ |
|
plMAXVertexAccNode *node; |
|
|
|
|
|
// See if one exists in this list |
|
for( node = fPointList[ index ]; node != nil; node = node->fNext ) |
|
{ |
|
if( node->IsEqual( normal, color, illum, uvs ) ) |
|
{ |
|
// Found match! Accumulate |
|
fIndices.Append( node->fIndex ); |
|
return; |
|
} |
|
} |
|
|
|
|
|
/// Adding new |
|
node = new plMAXVertexAccNode( point, normal, color, illum, fNumChannels, uvs, fNumVertices ); |
|
fInverseVertTable.Append( index ); |
|
fIndices.Append( fNumVertices++ ); |
|
|
|
node->fNext = fPointList[ index ]; |
|
fPointList[ index ] = node; |
|
} |
|
|
|
//// StuffMyData ///////////////////////////////////////////////////////////// |
|
// Stuffs the data into the given span. Assumes the span has already begun |
|
// creation. |
|
|
|
void plMAXVertexAccumulator::StuffMyData( plMaxNode* maxNode, plGeometrySpan *span, Mesh* mesh, ISkinContextData* skinData ) |
|
{ |
|
int i, j, origIdx; |
|
plMAXVertexAccNode *node; |
|
hsPoint3 *uvs[ plGeometrySpan::kMaxNumUVChannels ]; |
|
|
|
const char* dbgNodeName = maxNode->GetName(); |
|
TempWeightInfo *weights = nil; |
|
|
|
/// Precalculate the weights if necessary |
|
weights = new TempWeightInfo[ fNumPoints ]; |
|
if( skinData != nil ) |
|
{ |
|
for( i = 0; i < fNumPoints; i++ ) |
|
{ |
|
IFindSkinWeights( skinData, i, weights[ i ].fWeights, &weights[ i ].fIndices ); |
|
|
|
/// Debug checking stuff, for testing only |
|
// Wrong. Geometry span depends on this |
|
switch( span->fFormat & plGeometrySpan::kSkinWeightMask ) |
|
{ |
|
case plGeometrySpan::kSkin1Weight: |
|
weights[ i ].fWeights[1] = -1.f; |
|
break; |
|
case plGeometrySpan::kSkin2Weights: |
|
weights[ i ].fWeights[2] = -1.f; |
|
break; |
|
case plGeometrySpan::kSkin3Weights: |
|
weights[ i ].fWeights[3] = -1.f; |
|
break; |
|
default: |
|
hsAssert(false, "Shouldn't have gotten here"); |
|
|
|
break; |
|
} |
|
} |
|
} |
|
else if( maxNode->NumBones() ) |
|
{ |
|
IFindAllUserSkinWeights(maxNode, mesh, weights); |
|
} |
|
else |
|
{ |
|
for( i = 0; i < fNumPoints; i++ ) |
|
{ |
|
weights[ i ].fWeights[ 0 ] = weights[ i ].fWeights[ 1 ] = |
|
weights[ i ].fWeights[ 2 ] = weights[ i ].fWeights[ 3 ] = -1.0f; |
|
weights[ i ].fIndices = 0; |
|
} |
|
} |
|
|
|
plMaxBoneMap *boneMap = maxNode->GetBoneMap(); |
|
if (boneMap) |
|
{ |
|
for (i = 0; i < fNumPoints; i++) |
|
{ |
|
uint8_t indices[4]; |
|
indices[0] = (weights[i].fIndices) & 0xff; |
|
indices[1] = (weights[i].fIndices >> 8) & 0xff; |
|
indices[2] = (weights[i].fIndices >> 16) & 0xff; |
|
indices[3] = (weights[i].fIndices >> 24) & 0xff; |
|
|
|
for (j = 0; j < 4; j++) |
|
{ |
|
//if (weights[i].fWeights[j] >= 0) |
|
//{ |
|
if (indices[j] != 0) |
|
{ |
|
plMaxNodeBase *bone = maxNode->GetBone(indices[j] - 1); |
|
char *dbgBoneName = bone->GetName(); |
|
indices[j] = boneMap->GetIndex(bone) + 1; |
|
} |
|
//} |
|
} |
|
weights[i].fIndices = (indices[0]) | |
|
(indices[1] << 8) | |
|
(indices[2] << 16) | |
|
(indices[3] << 24); |
|
} |
|
} |
|
|
|
float maxWgt = 0; |
|
float penWgt = 0; |
|
int16_t maxIdx = -1; |
|
int16_t penIdx = -1; |
|
// Find the highest two weighted bones. We'll use just these two to calculate our bounds. |
|
for( i = 0; i < fNumPoints; i++ ) |
|
{ |
|
if( weights[i].fIndices ) |
|
{ |
|
for( j = 0; j < 4; j++ ) |
|
{ |
|
if( weights[i].fWeights[j] < 0 ) |
|
break; |
|
if( weights[i].fWeights[j] > maxWgt ) |
|
{ |
|
penWgt = maxWgt; |
|
penIdx = maxIdx; |
|
|
|
maxWgt = weights[i].fWeights[j]; |
|
maxIdx = (weights[i].fIndices >> (j*8)) & 0xff; |
|
} |
|
else |
|
if( weights[i].fWeights[j] > penWgt ) |
|
{ |
|
penWgt = weights[i].fWeights[j]; |
|
penIdx = (weights[i].fIndices >> (j*8)) & 0xff; |
|
} |
|
} |
|
} |
|
} |
|
if( maxIdx < 0 ) |
|
maxIdx = 0; |
|
if( penIdx < 0 ) |
|
penIdx = maxIdx; |
|
span->fMaxBoneIdx = maxIdx; |
|
span->fPenBoneIdx = penIdx; |
|
|
|
/// Stuff the verts |
|
for( i = 0; i < plGeometrySpan::kMaxNumUVChannels; i++) |
|
uvs[ i ] = nil; |
|
|
|
for( i = 0; i < fNumVertices; i++ ) |
|
{ |
|
origIdx = fInverseVertTable[ i ]; |
|
|
|
// origIdx gets us the list, but we need to know which node in the list |
|
for( node = fPointList[ origIdx ]; node != nil; node = node->fNext ) |
|
{ |
|
if( node->fIndex == i ) |
|
{ |
|
// Found it! output this one |
|
hsPoint3 normal; |
|
|
|
node->fNormal.Normalize(); |
|
normal.Set( node->fNormal.fX, node->fNormal.fY, node->fNormal.fZ ); |
|
for( j = 0; j < fNumChannels; j++ ) |
|
uvs[ j ] = &node->fUVs[ j ]; |
|
|
|
/// Add! |
|
span->AddVertex( &node->fPoint, &normal, node->fColor, node->fIllum, uvs, |
|
weights[ origIdx ].fWeights[ 0 ], weights[ origIdx ].fWeights[ 1 ], |
|
weights[ origIdx ].fWeights[ 2 ], weights[ origIdx ].fIndices ); |
|
break; |
|
} |
|
} |
|
hsAssert( node != nil, "Invalid accumulator table when stuffing buffers!" ); |
|
} |
|
|
|
/// Now stuff the indices |
|
for( i = 0; i < fIndices.GetCount(); i++ ) |
|
span->AddIndex( fIndices[ i ] ); |
|
|
|
if( weights != nil ) |
|
delete [] weights; |
|
} |
|
|
|
// IFindAllUserSkinWeights |
|
// Like IFindSkinWeights, but doesn't use Max's native skinning (e.g. ISkinContextData). |
|
// Rather, someone has put a bone (currently only support one) on this plMaxNode, |
|
// and told us what vertex channel to find the weight for that bone in. |
|
void plMAXVertexAccumulator::IFindAllUserSkinWeights( plMaxNode* node, Mesh* mesh, TempWeightInfo weights[]) |
|
{ |
|
const char* dbgNodeName = node->GetName(); |
|
|
|
int iMap = MAP_ALPHA; // FISH HACK, till we stuff the src channel into the max node. |
|
iMap = 66; |
|
|
|
int iChan = 1; // FISH HACK, get this one stuffed too. Could probably or them into the same |
|
//thing or something, but who cares. Gotta stop with the ethers. |
|
|
|
UVVert *wgtMap = mesh->mapVerts(iMap); |
|
int numWgtVerts = mesh->getNumMapVerts(iMap); |
|
|
|
TVFace* mapFaces = mesh->mapFaces(iMap); |
|
|
|
if( wgtMap && mapFaces ) |
|
{ |
|
Face* faces = mesh->faces; |
|
|
|
int i; |
|
for( i = 0; i < mesh->getNumFaces(); i++ ) |
|
{ |
|
int j; |
|
for( j = 0; j < 3; j++ ) |
|
{ |
|
int iVtx = faces[i].getVert(j); |
|
int iTvtx = mapFaces[i].getTVert(j); |
|
|
|
weights[iVtx].fWeights[2] = weights[iVtx].fWeights[3] = 0; |
|
weights[iVtx].fWeights[0] = 1.f - wgtMap[iTvtx][iChan]; |
|
if( weights[iVtx].fWeights[0] > 1.f ) |
|
weights[iVtx].fWeights[0] = 1.f; |
|
else if( weights[iVtx].fWeights[0] < 0 ) |
|
weights[iVtx].fWeights[0] = 0; |
|
weights[iVtx].fWeights[1] = 1.f - weights[iVtx].fWeights[0]; |
|
weights[iVtx].fIndices = 1 << 8; |
|
} |
|
} |
|
} |
|
else |
|
{ |
|
int i; |
|
for( i = 0; i < mesh->getNumVerts(); i++ ) |
|
{ |
|
weights[i].fWeights[1] = weights[i].fWeights[2] = weights[i].fWeights[3] = 0; |
|
weights[i].fWeights[0] = 1.f; |
|
weights[i].fIndices = 1 << 8; |
|
} |
|
} |
|
} |
|
|
|
//// IFindSkinWeights //////////////////////////////////////////////////////// |
|
// Finds the biggest weights (up to 4) for the given vertex index and returns |
|
// them, along with a uint32_t specifying the indices for each. |
|
|
|
void plMAXVertexAccumulator::IFindSkinWeights( ISkinContextData *skinData, |
|
int vertex, |
|
float *weights, uint32_t *indices ) |
|
{ |
|
float tempWs[ 4 ], tempW, t; |
|
uint32_t idxs[ 4 ], tempIdx, tI; |
|
int i, j; |
|
|
|
tempWs[ 0 ] = tempWs[ 1 ] = tempWs[ 2 ] = tempWs[ 3 ] = 0; |
|
idxs[ 0 ] = idxs[ 1 ] = idxs[ 2 ] = idxs[ 3 ] = 0; |
|
|
|
int boneCount = skinData->GetNumAssignedBones( vertex); |
|
|
|
if( boneCount ) |
|
{ |
|
float defWgt = 1.f; |
|
for( i = 0; i < boneCount; i++ ) |
|
{ |
|
/// Grab the weight and index for this bone |
|
tempW = skinData->GetBoneWeight( vertex, i ); |
|
defWgt -= tempW; |
|
|
|
// GetAssignedBone will assert unpredictably if the weight is 0.0f (exactly) |
|
// It will usually then return 0 for the bone index, but sometimes 1 |
|
// In any case, the bone index should not matter at that point. |
|
// Without walking through all the downstream code, seems to work ok. |
|
if(tempW > 0.0f) |
|
tempIdx = skinData->GetAssignedBone( vertex, i ) + 1; |
|
else |
|
tempIdx = 0; |
|
|
|
// float hi = skinData->GetBoneWeight( vertex, tempIdx ); |
|
|
|
/// Slide it in to our list |
|
for( j = 0; j < 4; j++ ) |
|
{ |
|
if( tempWs[ j ] < tempW ) |
|
{ |
|
t = tempWs[ j ]; |
|
tempWs[ j ] = tempW; |
|
tempW = t; |
|
|
|
tI = idxs[ j ]; |
|
idxs[ j ] = tempIdx; |
|
tempIdx = tI; |
|
} |
|
} |
|
} |
|
|
|
// This isn't really what we want. If the weights add up to less than |
|
// 1.f, the remainder is the un-skinned, un-boned Transform. |
|
// If the weights add up to more than 1.f, someone probably screwed up, |
|
// but we'll deal with it gracefully by normalizing. |
|
if( defWgt > 0 ) |
|
{ |
|
tempW = defWgt; |
|
tempIdx = 0; |
|
|
|
/// Slide it in to our list |
|
for( j = 0; j < 4; j++ ) |
|
{ |
|
if( tempWs[ j ] < tempW ) |
|
{ |
|
t = tempWs[ j ]; |
|
tempWs[ j ] = tempW; |
|
tempW = t; |
|
|
|
tI = idxs[ j ]; |
|
idxs[ j ] = tempIdx; |
|
tempIdx = tI; |
|
} |
|
} |
|
} |
|
|
|
t = tempWs[ 0 ] + tempWs[ 1 ] + tempWs[ 2 ] + tempWs[ 3 ]; |
|
t = 1.0f / t; |
|
|
|
weights[ 0 ] = tempWs[ 0 ] * t; |
|
weights[ 1 ] = tempWs[ 1 ] * t; |
|
weights[ 2 ] = tempWs[ 2 ] * t; |
|
weights[ 3 ] = tempWs[ 3 ] * t; |
|
} |
|
else |
|
{ |
|
weights[ 0 ] = 1.f; |
|
idxs[ 0 ] = 0; |
|
|
|
weights[1] = weights[2] = weights[3] = 0; |
|
idxs[1] = idxs[2] = idxs[3] = 0; |
|
} |
|
|
|
if( skinData->GetNumAssignedBones( vertex ) < 2 ) |
|
{ |
|
if( idxs[0] ) |
|
{ |
|
float tWgt = weights[0]; |
|
int tIdx = idxs[0]; |
|
|
|
weights[0] = weights[1]; |
|
idxs[0] = idxs[1]; |
|
|
|
weights[1] = tWgt; |
|
idxs[1] = tIdx; |
|
} |
|
} |
|
|
|
*indices = ( idxs[ 0 ] & 0xff ) | ( ( idxs[ 1 ] & 0xff ) << 8 ) | |
|
( ( idxs[ 2 ] & 0xff ) << 16 ) | ( ( idxs[ 3 ] & 0xff ) << 24 ); |
|
} |
|
|
|
int plMAXVertexAccumulator::GetVertexCount() |
|
{ |
|
// return fIndices.GetCount(); |
|
return fNumVertices; |
|
} |
|
|
|
void SetWaterColor(plGeometrySpan* span) |
|
{ |
|
plAccessGeometry accGeom; |
|
// First, set up our access, iterators and that mess. |
|
plAccessSpan acc; |
|
accGeom.AccessSpanFromGeometrySpan(acc, span); |
|
if( !acc.HasAccessTri() ) |
|
{ |
|
plAccessGeometry::Instance()->Close(acc); |
|
return; |
|
} |
|
plAccessTriSpan& tri = acc.AccessTri(); |
|
plAccTriIterator triIter(&tri); |
|
|
|
const int nVerts = tri.VertCount(); |
|
// Now, set up our accumulators |
|
hsTArray<float> lens; |
|
lens.SetCount(nVerts); |
|
memset(lens.AcquireArray(), 0, nVerts * sizeof(float)); |
|
hsTArray<float> wgts; |
|
wgts.SetCount(nVerts); |
|
memset(wgts.AcquireArray(), 0, nVerts * sizeof(float)); |
|
|
|
// For each triangle |
|
for( triIter.Begin(); triIter.More(); triIter.Advance() ) |
|
{ |
|
// This area thing seems like a good robust idea, but it doesn't really |
|
// take into account the fact that the sampling frequency is really determined |
|
// by the weakest link, or in this case the longest link. Experimenting |
|
// with alternatives. |
|
// Actually, I just realized that the area way kind of sucks, because, |
|
// as a parallelogram gets less and less rectangular, the area goes down |
|
// even as the longest edge (the diagonal) gets longer. |
|
float lenSq20 = hsVector3(&triIter.Position(2), &triIter.Position(0)).MagnitudeSquared(); |
|
float lenSq10 = hsVector3(&triIter.Position(1), &triIter.Position(0)).MagnitudeSquared(); |
|
float lenSq21 = hsVector3(&triIter.Position(2), &triIter.Position(1)).MagnitudeSquared(); |
|
float len = lenSq20; |
|
if( len < lenSq10 ) |
|
len = lenSq10; |
|
if( len < lenSq21 ) |
|
len = lenSq21; |
|
len = sqrt(len); |
|
|
|
lens[triIter.RawIndex(0)] += len; |
|
wgts[triIter.RawIndex(0)] += 1.f; |
|
|
|
lens[triIter.RawIndex(1)] += len; |
|
wgts[triIter.RawIndex(1)] += 1.f; |
|
|
|
lens[triIter.RawIndex(2)] += len; |
|
wgts[triIter.RawIndex(2)] += 1.f; |
|
|
|
} |
|
// For each vert |
|
int iVert; |
|
for( iVert = 0; iVert < nVerts; iVert++ ) |
|
{ |
|
if( wgts[iVert] > 0.f ) |
|
lens[iVert] /= wgts[iVert]; |
|
|
|
wgts[iVert] = 0.f; // We'll use them again on smoothing. |
|
} |
|
// Now we might want to smooth this out some |
|
// This can be repeated for any degree of smoothing |
|
hsTArray<float> smLens; |
|
smLens.SetCount(nVerts); |
|
memset(smLens.AcquireArray(), 0, nVerts * sizeof(float)); |
|
// For each triangle |
|
for( triIter.Begin(); triIter.More(); triIter.Advance() ) |
|
{ |
|
int i; |
|
// For each edge |
|
for( i = 0; i < 3; i++ ) |
|
{ |
|
int iVert = triIter.RawIndex(i); |
|
int iVertNext = triIter.RawIndex(i < 2 ? i+1 : 0); |
|
int iVertLast = triIter.RawIndex(i ? i-1 : 2); |
|
smLens[iVert] += lens[iVert]; |
|
wgts[iVert] += 1.f; |
|
|
|
const float kSmooth(8.f); |
|
smLens[iVertNext] += lens[iVert] * kSmooth; |
|
wgts[iVertNext] += kSmooth; |
|
|
|
smLens[iVertLast] += lens[iVert] * kSmooth; |
|
wgts[iVertLast] += kSmooth; |
|
} |
|
} |
|
lens.Swap(smLens); |
|
// For each vert |
|
for( iVert = 0; iVert < nVerts; iVert++ ) |
|
{ |
|
if( wgts[iVert] > 0.f ) |
|
lens[iVert] /= wgts[iVert]; |
|
|
|
wgts[iVert] = 0.f; // We'll use them again on smoothing. |
|
} |
|
|
|
plConst(float) kNumLens(4.f); |
|
// Okay, we have smoothed lengths. We just need to |
|
// iterate over the vertices and stuff 1/len into the alpha channel |
|
// For each vert |
|
plAccDiffuseIterator colIter(&tri); |
|
for( iVert = 0, colIter.Begin(); colIter.More(); iVert++, colIter.Advance() ) |
|
{ |
|
hsColorRGBA multCol; |
|
hsColorRGBA addCol; |
|
span->ExtractInitColor(iVert, &multCol, &addCol); |
|
|
|
// Get the vert color |
|
hsColorRGBA col = colIter.DiffuseRGBA(); |
|
col = multCol; |
|
|
|
col.a = lens[iVert] > 0.f ? 1.f / (kNumLens * lens[iVert]) : 1.f; |
|
|
|
// Stuff color back in. |
|
*colIter.Diffuse32() = col.ToARGB32(); |
|
} |
|
|
|
// Close up the access span. |
|
accGeom.Close(acc); |
|
} |
|
|
|
void SetWaterColor(hsTArray<plGeometrySpan*>& spans) |
|
{ |
|
int i; |
|
for( i = 0; i < spans.GetCount(); i++ ) |
|
SetWaterColor(spans[i]); |
|
}
|
|
|