|
|
|
/*==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/>.
|
|
|
|
|
|
|
|
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 "hsTypes.h"
|
|
|
|
#include "Max.h"
|
|
|
|
#include "iparamb2.h"
|
|
|
|
#include "modstack.h"
|
|
|
|
#include "ISkin.h"
|
|
|
|
#include "meshdlib.h"
|
|
|
|
|
|
|
|
|
|
|
|
#include "HeadSpin.h"
|
|
|
|
#include "../CoreLib/hsBitVector.h"
|
|
|
|
#include "plMeshConverter.h"
|
|
|
|
#include "hsResMgr.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 "hsExceptionStack.h"
|
|
|
|
#include "../MaxPlasmaMtls/Materials/plCompositeMtl.h"
|
|
|
|
#include "../MaxPlasmaMtls/Materials/plPassMtl.h"
|
|
|
|
#include "../MaxPlasmaMtls/Materials/plCompositeMtlPB.h"
|
|
|
|
#include "../MaxPlasmaMtls/Materials/plPassMtlBasicPB.h"
|
|
|
|
#include "../plPipeline/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"
|
|
|
|
|
|
|
|
#include "plTweak.h"
|
|
|
|
|
|
|
|
//// Static Members //////////////////////////////////////////////////////////
|
|
|
|
|
|
|
|
hsBool plMeshConverter::fWarnBadNormals = true;
|
|
|
|
char plMeshConverter::fWarnBadNormalsMsg[] = "Bad normal autogeneration - please deliver Max file to QA";
|
|
|
|
|
|
|
|
hsBool 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";
|
|
|
|
|
|
|
|
hsBool 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 fIndices;
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
class plMAXVertexAccNode
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
hsPoint3 fPoint; // Inefficient space-wise, I know, but it makes this a lot simpler...
|
|
|
|
hsVector3 fNormal;
|
|
|
|
hsColorRGBA fColor, fIllum;
|
|
|
|
UInt32 fIndex;
|
|
|
|
hsPoint3 fUVs[ plGeometrySpan::kMaxNumUVChannels ];
|
|
|
|
UInt32 fNumChannels;
|
|
|
|
|
|
|
|
plMAXVertexAccNode *fNext;
|
|
|
|
|
|
|
|
plMAXVertexAccNode( const hsPoint3 *point, const hsVector3 *normal, const hsColorRGBA& color, const hsColorRGBA& illum, int numChannels, const hsPoint3 *uvs, UInt32 index );
|
|
|
|
|
|
|
|
hsBool 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> fIndices;
|
|
|
|
hsTArray<UInt32> fInverseVertTable;
|
|
|
|
|
|
|
|
void IFindSkinWeights( ISkinContextData *skinData, int vertex, float *weights, UInt32 *indices );
|
|
|
|
void IFindUserSkinWeights( plMaxNode* node, Mesh* mesh, int vertex, float *weights, UInt32 *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 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 = TRACKED_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( hsBool 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( hsBool 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 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 numFaces, i, j, numVerts;
|
|
|
|
hsMatrix44 l2wMatrix, vert2LMatrix, vertInvTransMatrix, tempMatrix;
|
|
|
|
hsBool flipOrder, checkForOverflow = false;
|
|
|
|
|
|
|
|
/// Get da mesh
|
|
|
|
mesh = IGetNodeMesh( node );
|
|
|
|
if( mesh == nil )
|
|
|
|
return nil;
|
|
|
|
|
|
|
|
numFaces = mesh->getNumFaces();
|
|
|
|
numVerts = mesh->getNumVerts();
|
|
|
|
|
|
|
|
plConvexVolume *bounds = TRACKED_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 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 = TRACKED_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)
|
|
|
|
{
|
|
|
|
TSTR logfile = "UV_";
|
|
|
|
logfile += GetCOREInterface()->GetCurFileName();
|
|
|
|
logfile += ".log";
|
|
|
|
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.
|
|
|
|
|
|
|
|
hsBool plMeshConverter::CreateSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray, bool doPreshading )
|
|
|
|
{
|
|
|
|
hsGuardBegin( "plMeshConverter::CreateSpans" );
|
|
|
|
|
|
|
|
const char* dbgNodeName = node->GetName();
|
|
|
|
Mesh *mesh;
|
|
|
|
Int32 numFaces, i, j, k, numVerts, maxNumBones, maxUVWSrc;
|
|
|
|
Int32 numMaterials = 1, numSubMaterials = 1;
|
|
|
|
hsMatrix44 l2wMatrix, vert2LMatrix, vertInvTransMatrix, tempMatrix;
|
|
|
|
Mtl *maxMaterial = nil;
|
|
|
|
hsBool isComposite, isMultiMat, flipOrder, checkForOverflow = false, includesComp;
|
|
|
|
UInt8 ourFormat, numChannels, maxBlendChannels;
|
|
|
|
hsColorRGBA *colorArray = nil;
|
|
|
|
hsColorRGBA *illumArray = nil;
|
|
|
|
UInt32 sharedSpanProps = 0;
|
|
|
|
hsBitVector usedSubMtls;
|
|
|
|
hsBool 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 hsBool 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;
|
|
|
|
}
|
|
|
|
hsScalar 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);
|
|
|
|
hsBool allWhite = true, allBlack = true;
|
|
|
|
|
|
|
|
if( mesh->numCVerts > 0)
|
|
|
|
{
|
|
|
|
if (mesh->vertCol != nil)
|
|
|
|
{
|
|
|
|
colorArray = TRACKED_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 = TRACKED_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 = TRACKED_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 (hsBool)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 (hsBool)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 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 = TRACKED_NEW hsTArray<plMAXVertNormal>[numMaterials];
|
|
|
|
vertDPosDvCache = TRACKED_NEW hsTArray<plMAXVertNormal>[numMaterials];
|
|
|
|
|
|
|
|
hsTArray<Int16> bumpLayIdx;
|
|
|
|
hsTArray<Int16> bumpLayChan;
|
|
|
|
hsTArray<Int16> bumpDuChan;
|
|
|
|
hsTArray<Int16> 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 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 = TRACKED_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();
|
|
|
|
|
|
|
|
hsScalar 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 )
|
|
|
|
{
|
|
|
|
hsBool needMoreDicing = false;
|
|
|
|
int i;
|
|
|
|
for( i = 0; i < spanArray.GetCount(); i++ )
|
|
|
|
{
|
|
|
|
plAccessGeometry accGeom;
|
|
|
|
plAccessSpan accSpan;
|
|
|
|
accGeom.AccessSpanFromGeometrySpan(accSpan, spanArray[i]);
|
|
|
|
hsBool 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 (hsBool)true;
|
|
|
|
}
|
|
|
|
catch( hsBool 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 plMeshConverter::ICreateHexColor( float r, float g, float b )
|
|
|
|
{
|
|
|
|
UInt32 ru, gu, bu, au;
|
|
|
|
|
|
|
|
|
|
|
|
au = 0xff000000;
|
|
|
|
ru = r * 255.0f;
|
|
|
|
gu = g * 255.0f;
|
|
|
|
bu = b * 255.0f;
|
|
|
|
return au | ( ru << 16 ) | ( gu << 8 ) | ( bu );
|
|
|
|
}
|
|
|
|
|
|
|
|
UInt32 plMeshConverter::ISetHexAlpha( UInt32 color, float alpha)
|
|
|
|
{
|
|
|
|
UInt32 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 = TRACKED_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;
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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>& bumpLayIdx,
|
|
|
|
hsTArray<Int16>& bumpLayChan,
|
|
|
|
hsTArray<Int16>& bumpDuChan,
|
|
|
|
hsTArray<Int16>& 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>& bumpLayIdx,
|
|
|
|
hsTArray<Int16>& bumpLayChan,
|
|
|
|
hsTArray<Int16>& bumpDuChan,
|
|
|
|
hsTArray<Int16>& 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 uvChan, hsTArray<plMAXVertNormal>& vertDPosDuvCache, TVFace* tvFace, UInt32 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>& bumpLayIdx, hsTArray<Int16>& bumpLayChan,
|
|
|
|
hsTArray<plMAXVertNormal>* vertDPosDuCache, hsTArray<plMAXVertNormal>* vertDPosDvCache)
|
|
|
|
{
|
|
|
|
const char* dbgNodeName = node->GetName();
|
|
|
|
|
|
|
|
Mtl* mainMtl = hsMaterialConverter::Instance().GetBaseMtl( node );
|
|
|
|
|
|
|
|
hsBool needsGradientUvs = hsMaterialConverter::Instance().HasBumpLayer(node, mainMtl) || node->GetWaterDecEnv();
|
|
|
|
|
|
|
|
if( needsGradientUvs )
|
|
|
|
{
|
|
|
|
int matIdx;
|
|
|
|
for( matIdx = 0; matIdx < ourMaterials.GetCount(); matIdx++ )
|
|
|
|
{
|
|
|
|
if( bumpLayIdx[matIdx] >= 0 )
|
|
|
|
{
|
|
|
|
UInt32 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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsBool 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 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();
|
|
|
|
|
|
|
|
hsBool 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 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.
|
|
|
|
|
|
|
|
hsBool 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 = TRACKED_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 = TRACKED_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 = TRACKED_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 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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hsScalar maxWgt = 0;
|
|
|
|
hsScalar penWgt = 0;
|
|
|
|
Int16 maxIdx = -1;
|
|
|
|
Int16 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 dword specifying the indices for each.
|
|
|
|
|
|
|
|
void plMAXVertexAccumulator::IFindSkinWeights( ISkinContextData *skinData,
|
|
|
|
int vertex,
|
|
|
|
float *weights, UInt32 *indices )
|
|
|
|
{
|
|
|
|
float tempWs[ 4 ], tempW, t;
|
|
|
|
UInt32 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 )
|
|
|
|
{
|
|
|
|
hsScalar 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<hsScalar> lens;
|
|
|
|
lens.SetCount(nVerts);
|
|
|
|
memset(lens.AcquireArray(), 0, nVerts * sizeof(hsScalar));
|
|
|
|
hsTArray<hsScalar> wgts;
|
|
|
|
wgts.SetCount(nVerts);
|
|
|
|
memset(wgts.AcquireArray(), 0, nVerts * sizeof(hsScalar));
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
hsScalar lenSq20 = hsVector3(&triIter.Position(2), &triIter.Position(0)).MagnitudeSquared();
|
|
|
|
hsScalar lenSq10 = hsVector3(&triIter.Position(1), &triIter.Position(0)).MagnitudeSquared();
|
|
|
|
hsScalar lenSq21 = hsVector3(&triIter.Position(2), &triIter.Position(1)).MagnitudeSquared();
|
|
|
|
hsScalar len = lenSq20;
|
|
|
|
if( len < lenSq10 )
|
|
|
|
len = lenSq10;
|
|
|
|
if( len < lenSq21 )
|
|
|
|
len = lenSq21;
|
|
|
|
len = hsSquareRoot(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<hsScalar> smLens;
|
|
|
|
smLens.SetCount(nVerts);
|
|
|
|
memset(smLens.AcquireArray(), 0, nVerts * sizeof(hsScalar));
|
|
|
|
// 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 hsScalar 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(hsScalar) 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]);
|
|
|
|
}
|