You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
498 lines
14 KiB
498 lines
14 KiB
/*==LICENSE==* |
|
|
|
CyanWorlds.com Engine - MMOG client, server and tools |
|
Copyright (C) 2011 Cyan Worlds, Inc. |
|
|
|
This program is free software: you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation, either version 3 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
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 <commdlg.h> |
|
#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 <set> |
|
|
|
hsBool ConvertList(hsTArray<plMaxNode*>& 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<plMaxNode*>& 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; |
|
} |