/*==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 . 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 fIndices; hsTArray 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 *pos, hsTArray *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 &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 *> ourMaterials; hsTArray *> ourAccumulators; hsTArray vertNormalCache; hsTArray* vertDPosDuCache = nil; hsTArray* 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 *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 *currAccum = TRACKED_NEW hsTArray; 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[numMaterials]; vertDPosDvCache = TRACKED_NEW hsTArray[numMaterials]; hsTArray bumpLayIdx; hsTArray bumpLayChan; hsTArray bumpDuChan; hsTArray 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 *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 *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& 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 *>& ourMaterials, hsTArray& bumpLayIdx, hsTArray& bumpLayChan, hsTArray& bumpDuChan, hsTArray& 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 *>& ourMaterials, hsTArray& bumpLayIdx, hsTArray& bumpLayChan, hsTArray& bumpDuChan, hsTArray& 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& 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 *>& ourMaterials, hsTArray& bumpLayIdx, hsTArray& bumpLayChan, hsTArray* vertDPosDuCache, hsTArray* 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 lens; lens.SetCount(nVerts); memset(lens.AcquireArray(), 0, nVerts * sizeof(hsScalar)); hsTArray 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 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& spans) { int i; for( i = 0; i < spans.GetCount(); i++ ) SetWaterColor(spans[i]); }