/*==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==*/ // // 3DSMax HeadSpin exporter // #include "hsTypes.h" #include "Max.h" #include "istdplug.h" #include "Notify.h" #include #include "bmmlib.h" #include "INode.h" #include "plConvert.h" #include "hsResMgr.h" #include "hsTemplates.h" #include "hsConverterUtils.h" #include "hsControlConverter.h" #include "plMeshConverter.h" #include "hsMaterialConverter.h" #include "plLayerConverter.h" #include "UserPropMgr.h" #include "hsStringTokenizer.h" #include "../MaxExport/plErrorMsg.h" #include "hsVertexShader.h" #include "plLightMapGen.h" #include "plBitmapCreator.h" #include "plgDispatch.h" #include "../pnMessage/plTimeMsg.h" #include "../MaxComponent/plComponent.h" #include "../MaxMain/plMaxNode.h" #include "../plMessage/plNodeCleanupMsg.h" #include "../pnSceneObject/plSceneObject.h" #include "../MaxComponent/plClusterComponent.h" #include "../plPhysX/plSimulationMgr.h" #include "../MaxMain/plPhysXCooking.h" #include "../MaxExport/plExportProgressBar.h" #include "hsUtils.h" #include "../MaxMain/plGetLocationDlg.h" #ifdef HS_DEBUGGING #define HS_NO_TRY #endif plConvert::plConvert() : fWarned(0) { } plConvert& plConvert::Instance() { static plConvert theInstance; return theInstance; } hsBool plConvert::IOK() { return (!fQuit && !fpErrorMsg->IsBogus() ) ? true: false; } hsBool plConvert::Convert() { #ifndef HS_NO_TRY try #endif { fSettings->fReconvert = false; fWarned = 0; fInterface->SetIncludeXRefsInHierarchy(TRUE); plMaxNode *pNode = (plMaxNode *)fInterface->GetRootNode(); AddMessageToQueue(new plTransformMsg(nil, nil, nil, nil)); AddMessageToQueue(new plDelayedTransformMsg(nil, nil, nil, nil)); IFindDuplicateNames(); plExportProgressBar bar; hsBool retVal = true; // sometime, we might look at this if( !IAutoClusterRecur(pNode) ) { fQuit = true; } if(IOK()) { bar.Start("Clear Old Data"); retVal = pNode->DoAllRecur( plMaxNode::ClearData, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Convert Validate"); retVal = pNode->DoRecur( plMaxNode::ConvertValidate, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Components Initialize"); retVal = pNode->DoRecur( plMaxNode::SetupPropertiesPass, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Prepare for skinning"); retVal = pNode->DoRecur( plMaxNode::PrepareSkin, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Make Scene Object"); retVal = pNode->DoRecur( plMaxNode::MakeSceneObject, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Make Physical"); plPhysXCooking::Init(); retVal = pNode->DoRecur( plMaxNode::MakePhysical, fpErrorMsg, fSettings, &bar ); plPhysXCooking::Shutdown(); } if(IOK()) { bar.Start("Component Preconvert"); retVal = pNode->DoRecur( plMaxNode::FirstComponentPass, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Make Controller"); retVal = pNode->DoRecur( plMaxNode::MakeController, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // must be before mesh bar.Start("Make Coord Interface"); retVal = pNode->DoRecur( plMaxNode::MakeCoordinateInterface, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // must be after coord interface but before pool data is created. bar.Start("Make Connections"); retVal = pNode->DoRecur( plMaxNode::MakeParentOrRoomConnection, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // must be before simulation bar.Start("Make Mesh"); retVal = pNode->DoRecur( plMaxNode::MakeMesh, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // doesn't matter when bar.Start("Make Light"); retVal = pNode->DoRecur( plMaxNode::MakeLight, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // doesn't matter when bar.Start("Make Occluder"); retVal = pNode->DoRecur( plMaxNode::MakeOccluder, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // must be after mesh bar.Start("Make Modifiers"); retVal = pNode->DoRecur( plMaxNode::MakeModifiers, fpErrorMsg, fSettings, &bar ); } if(IOK()) { bar.Start("Convert Components"); retVal = pNode->DoRecur( plMaxNode::ConvertComponents, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // do this after convert bar.Start("Set Up Interface References"); retVal = pNode->DoRecur( plMaxNode::MakeIfaceReferences, fpErrorMsg, fSettings, &bar ); } if(IOK() && fSettings->fDoPreshade) { // These need to be opened after the components have had a chance to flag the MaxNodes plLightMapGen::Instance().Open(fInterface, fInterface->GetTime(), fSettings->fDoLightMap); hsVertexShader::Instance().Open(); bar.Start("Preshade Geometry"); retVal = pNode->DoRecur( plMaxNode::ShadeMesh, fpErrorMsg, fSettings, &bar ); plLightMapGen::Instance().Close(); hsVertexShader::Instance().Close(); } if(IOK()) { // Do this next-to-last--allows all the components to free up any temp data they kept around bar.Start("Component DeInit"); retVal = pNode->DoRecur( plMaxNode::DeInitComponents, fpErrorMsg, fSettings, &bar ); } if(IOK()) { // Do this very last--it de-inits and frees all the maxNodeDatas lying around bar.Start("Clear MaxNodeDatas"); retVal = pNode->DoAllRecur( plMaxNode::ClearMaxNodeData, fpErrorMsg, fSettings, &bar ); } // fpErrorMsg->Set(); DeInit(); fInterface->SetIncludeXRefsInHierarchy(FALSE); return IOK(); } #ifndef HS_NO_TRY catch(plErrorMsg& err) { DeInit(); fInterface->SetIncludeXRefsInHierarchy(FALSE); err.Show(); return false; } catch(...) { DeInit(); fInterface->SetIncludeXRefsInHierarchy(FALSE); fpErrorMsg->Set(true, "plConvert", "Unknown error during convert\n"); fpErrorMsg->Show(); return false; } #endif } //#include "../MaxMain/plMaxNodeData.h" //#include hsBool ConvertList(hsTArray& nodes, PMaxNodeFunc p, plErrorMsg *errMsg, plConvertSettings *settings) { for (int i = 0; i < nodes.Count(); i++) { (nodes[i]->*p)(errMsg, settings); if (errMsg && errMsg->IsBogus()) return false; } return true; } hsBool plConvert::Convert(hsTArray& nodes) { #ifndef HS_NO_TRY try #endif { fSettings->fReconvert = true; hsBool retVal = true; if (IOK()) retVal = ConvertList(nodes, plMaxNode::ClearData, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::ConvertValidate, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::SetupPropertiesPass, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::PrepareSkin, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::MakeSceneObject, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::FirstComponentPass, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::MakeController, fpErrorMsg,fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::MakeCoordinateInterface, fpErrorMsg, fSettings);// must be before mesh if(IOK()) retVal = ConvertList(nodes, plMaxNode::MakeParentOrRoomConnection, fpErrorMsg, fSettings); // after coord, before mesh (or any other pool data). // These shouldn't be opened until the components have had a chance to flag the MaxNodes plLightMapGen::Instance().Open(fInterface, fInterface->GetTime(), fSettings->fDoLightMap); hsVertexShader::Instance().Open(); if(IOK()) retVal = ConvertList(nodes, plMaxNode::MakeMesh, fpErrorMsg, fSettings); // must be before simulation if(IOK()) // doesn't matter when retVal = ConvertList(nodes, plMaxNode::MakeLight, fpErrorMsg, fSettings); if(IOK()) // doesn't matter when retVal = ConvertList(nodes, plMaxNode::MakeOccluder, fpErrorMsg, fSettings); if(IOK()) // must be after mesh retVal = ConvertList(nodes, plMaxNode::MakeModifiers, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::ConvertComponents, fpErrorMsg, fSettings); if(IOK()) retVal = ConvertList(nodes, plMaxNode::ShadeMesh, fpErrorMsg, fSettings); // These may be used by components, so don't close them till the end. plLightMapGen::Instance().Close(); hsVertexShader::Instance().Close(); plgDispatch::MsgSend(new plTransformMsg(nil, nil, nil, nil)); plgDispatch::MsgSend(new plDelayedTransformMsg(nil, nil, nil, nil)); DeInit(); return IOK(); } #ifndef HS_NO_TRY catch(plErrorMsg& err) { err.Show(); return false; } catch(...) { hsMessageBox("Unknown error during convert", "plConvert", hsMessageBoxNormal); return false; } #endif } hsBool plConvert::Init(Interface *ip, plErrorMsg* msg, plConvertSettings *settings) { fInterface = ip; fpErrorMsg = msg; fSettings = settings; // Move us to time 0, so that things like initial transforms are always consistent with the 0th frame. // This saves our asses from things like the patch-generation process later ip->SetTime( 0, false ); hsConverterUtils::Instance().Init(true, fpErrorMsg); plBitmapCreator::Instance().Init(true, fpErrorMsg); hsMaterialConverter::Instance().Init(true, fpErrorMsg); hsControlConverter::Instance().Init(fpErrorMsg); plMeshConverter::Instance().Init(true, fpErrorMsg); plLayerConverter::Instance().Init(true, fpErrorMsg); plGetLocationDlg::Instance().ResetDefaultLocation(); fQuit = false; return true; } void plConvert::DeInit() { // Undo any autogenerated clusters. IAutoUnClusterRecur(fInterface->GetRootNode()); // clear out the message queue for (int i = 0; i < fMsgQueue.Count(); i++) plgDispatch::MsgSend(fMsgQueue[i]); fMsgQueue.Reset(); hsControlConverter::Instance().DeInit(); plMeshConverter::Instance().DeInit(); plLayerConverter::Instance().DeInit(); // Moving this to the end of writing the files out. Yes, this means that any unused mipmaps still get // written to disk, including ones loaded on preload, but it's the only way to get shared texture pages // to work without loading in the entire age worth of reffing objects. - 5.30.2002 mcn // plBitmapCreator::Instance().DeInit(); plNodeCleanupMsg *clean = TRACKED_NEW plNodeCleanupMsg(); plgDispatch::MsgSend( clean ); } void plConvert::AddMessageToQueue(plMessage* msg) { fMsgQueue.Append(msg); } void plConvert::SendEnvironmentMessage(plMaxNode* pNode, plMaxNode* efxRegion, plMessage* msg, hsBool ignorePhysicals ) { for (int i = 0; i < pNode->NumberOfChildren(); i++) SendEnvironmentMessage((plMaxNode *)pNode->GetChildNode(i), efxRegion, msg, ignorePhysicals ); // don't call ourself... if (pNode == efxRegion) return; // send the scene object this message: if (efxRegion->Contains( ((INode*)pNode)->GetNodeTM(hsConverterUtils::Instance().GetTime(pNode->GetInterface())).GetRow(3)) && pNode->GetSceneObject() && ( !ignorePhysicals || !pNode->IsPhysical() ) ) msg->AddReceiver( pNode->GetSceneObject()->GetKey() ); } plMaxNode* plConvert::GetRootNode() { return (plMaxNode *)fInterface->GetRootNode(); } BOOL plConvert::IAutoClusterRecur(INode* node) { plMaxNode* maxNode = (plMaxNode*)node; plComponentBase* comp = maxNode->ConvertToComponent(); if( comp && (comp->ClassID() == CLUSTER_COMP_CID) ) { plClusterComponent* clust = (plClusterComponent*)comp; // Cluster decides if it needs autogen if( clust->AutoGen(fpErrorMsg) ) return false; } int i; for( i = 0; i < node->NumberOfChildren(); i++ ) { if( !IAutoClusterRecur(node->GetChildNode(i)) ) return false; } return true; } BOOL plConvert::IAutoUnClusterRecur(INode* node) { plMaxNode* maxNode = (plMaxNode*)node; plComponentBase* comp = maxNode->ConvertToComponent(); if( comp && (comp->ClassID() == CLUSTER_COMP_CID) ) { plClusterComponent* clust = (plClusterComponent*)comp; // Cluster remembers whether it was autogen'd. clust->AutoClear(fpErrorMsg); } int i; for( i = 0; i < node->NumberOfChildren(); i++ ) { IAutoUnClusterRecur(node->GetChildNode(i)); } return true; } bool plConvert::IFindDuplicateNames() { INode *node = fInterface->GetRootNode(); const char *name = ISearchNames(node, node); if (!name) return false; fpErrorMsg->Set(true, "Error in Conversion of Scene Objects", "Two objects in the scene share the name '%s'.\nUnique names are necessary during the export process.\n", name ); fpErrorMsg->Show(); return true; } // Recursivly search nodes for duplicate names, and return when one is found const char *plConvert::ISearchNames(INode *node, INode *root) { int count = ICountNameOccurances(root, node->GetName()); if (count > 1) return node->GetName(); for (int i = 0; i < node->NumberOfChildren(); i++) { const char *name = ISearchNames(node->GetChildNode(i), root); if (name) return name; } return nil; } // Recursivly search nodes for this name, and return the number of times found int plConvert::ICountNameOccurances(INode *node, const char *name) { int count = 0; if (!stricmp(name, node->GetName())) count++; for (int i = 0; i < node->NumberOfChildren(); i++) count += ICountNameOccurances(node->GetChildNode(i), name); return count; }