diff --git a/MaxNifImport/AppSettings.cpp b/MaxNifImport/AppSettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e21bb7460befd237af6b5408c4a1012eb09c8a38 --- /dev/null +++ b/MaxNifImport/AppSettings.cpp @@ -0,0 +1,102 @@ +#include "stdafx.h" +#include <io.h> +#include <string.h> +#include <tchar.h> +#include "AppSettings.h" + +AppSettingsMap TheAppSettings; + +void AppSettings::Initialize(Interface *gi) +{ + TCHAR iniName[MAX_PATH]; + LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR); + PathCombine(iniName, pluginDir, "MaxNifTools.ini"); + if (-1 != _taccess(iniName, 0)) { + bool reparse = GetIniValue<bool>("System", "Reparse", false, iniName); + if (reparse || TheAppSettings.empty()){ + TheAppSettings.clear(); + } + + string Applications = GetIniValue<string>("System", "KnownApplications", "", iniName); + stringlist apps = TokenizeString(Applications.c_str(), ";"); + for (stringlist::iterator appstr=apps.begin(); appstr != apps.end(); ++appstr){ + AppSettingsMap::iterator itr = TheAppSettings.find(*appstr); + if (itr == TheAppSettings.end()){ + itr = TheAppSettings.insert(TheAppSettings.end(), AppSettingsMap::value_type(*appstr, AppSettings(*appstr)) ); + (*itr).second.ReadSettings(iniName); + } + } + } +} + +void AppSettings::ReadSettings(string iniFile) +{ + NameValueCollection settings = ReadIniSection(Name.c_str(), iniFile.c_str()); + + // expand indirect values first + for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr) + itr->second = GetIndirectValue(itr->second.c_str()); + + // next expand qualifiers + for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr) + itr->second = ExpandQualifiers(itr->second.c_str(), settings); + + // finally expand environment variables, last because it clobbers my custom qualifier expansion + for (NameValueCollection::iterator itr = settings.begin(), end = settings.end(); itr != end; ++itr) + itr->second = ExpandEnvironment(itr->second); + + std::swap(Environment, settings); + + rootPath = GetSetting<string>("RootPath"); + + string searchPathString = GetSetting<string>("TextureSearchPaths"); + searchPaths = TokenizeString(searchPathString.c_str(), ";"); + + string extensionString = GetSetting<string>("TextureExtensions"); + extensions = TokenizeString(extensionString.c_str(), ";"); + + string texRootPathString = GetSetting<string>("TextureRootPaths"); + rootPaths = TokenizeString(texRootPathString.c_str(), ";"); + + Skeleton = GetSetting<string>("Skeleton"); +} + +string AppSettings::FindImage(const string& fname){ + TCHAR buffer[MAX_PATH]; + + // Simply check for fully qualified path + if (PathIsRoot(fname.c_str())) { + if (-1 != _taccess(fname.c_str(), 0)) + return fname; + } + + // Test if its relative and in one of the specified root paths + for (stringlist::iterator itr = rootPaths.begin(), end = rootPaths.end(); itr != end; ++itr ){ + PathCombine(buffer, itr->c_str(), fname.c_str()); + if (-1 != _taccess(buffer, 0)){ + return string(buffer); + } + } + + // Hit the directories to find out whats out there + if (!parsedImages){ + FindImages(imgTable, rootPath, searchPaths, extensions); + parsedImages = true; + } + + // Search my filename for our texture + _tcscpy(buffer, PathFindFileName(fname.c_str())); + PathRemoveExtension(buffer); + NameValueCollection::iterator itr = imgTable.find(buffer); + if (itr != imgTable.end()){ + if (!rootPath.empty()) { + _tcscpy(buffer, rootPath.c_str()); + PathCombine(buffer, rootPath.c_str(), ((*itr).second).c_str()); + return string(buffer); + } else { + return (*itr).second; + } + } + return fname; +} + diff --git a/MaxNifImport/AppSettings.h b/MaxNifImport/AppSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..ebfb17da52cd87bfae8002d30d15ebcbe92519dc --- /dev/null +++ b/MaxNifImport/AppSettings.h @@ -0,0 +1,82 @@ +/********************************************************************** +*< +FILE: AppSettings.h + +DESCRIPTION: AppSetting helper class for managing game specific + file settings. + +CREATED BY: tazpn (Theo) + +HISTORY: + +*> Copyright (c) 2006, All Rights Reserved. +**********************************************************************/ +#ifndef _APPSETTINGS_H_ +#define _APPSETTINGS_H_ + +#include "niutils.h" + +class AppSettings +{ +public: + AppSettings(const std::string& name) : Name(name), parsedImages(false) {} + + std::string Name; + std::string rootPath; + bool parsedImages; + stringlist searchPaths; + stringlist rootPaths; + stringlist extensions; + std::string Skeleton; + NameValueCollection Environment; + NameValueCollection imgTable; + + static void Initialize(Interface *gi); + void ReadSettings(std::string iniFile); + std::string FindImage(const std::string& fname); + + template<typename T> + inline T GetSetting(std::string setting){ + T v; + NameValueCollection::iterator itr = Environment.find(setting); + if (itr != Environment.end()){ + stringstream sstr((*itr).second); + sstr >> v; + } + return v; + } + template<> + inline std::string GetSetting(std::string setting){ + NameValueCollection::iterator itr = Environment.find(setting); + if (itr != Environment.end()) + return (*itr).second; + return std::string(); + } + + template<typename T> + inline T GetSetting(std::string setting, T Default){ + NameValueCollection::iterator itr = Environment.find(setting); + if (itr != Environment.end()){ + T v; + stringstream sstr((*itr).second); + sstr >> v; + return v; + } + return Default; + } + template<> + inline std::string GetSetting(std::string setting, std::string Default){ + NameValueCollection::iterator itr = Environment.find(setting); + if (itr != Environment.end()) + return (*itr).second; + return Default; + } +}; + +typedef std::map<std::string, AppSettings, ltstr> AppSettingsMap; + +// The Global Map +// Global so that I dont have to parse the texture directories on every import +extern AppSettingsMap TheAppSettings; + +#endif //_APPSETTINGS_H_ \ No newline at end of file diff --git a/MaxNifImport/DllEntry.cpp b/MaxNifImport/DllEntry.cpp new file mode 100644 index 0000000000000000000000000000000000000000..97160e9db168ef437b052836c17edb50dedbf6d3 --- /dev/null +++ b/MaxNifImport/DllEntry.cpp @@ -0,0 +1,78 @@ +/********************************************************************** + *< + FILE: DllEntry.cpp + + DESCRIPTION: Contains the Dll Entry stuff + + CREATED BY: tazpn (Theo) + + HISTORY: + + *> Copyright (c) 2006, All Rights Reserved. + **********************************************************************/ +#include "MaxNifImport.h" + +extern ClassDesc2* GetMaxNifImportDesc(); + +HINSTANCE hInstance; +int controlsInit = FALSE; + +// This function is called by Windows when the DLL is loaded. This +// function may also be called many times during time critical operations +// like rendering. Therefore developers need to be careful what they +// do inside this function. In the code below, note how after the DLL is +// loaded the first time only a few statements are executed. + +BOOL WINAPI DllMain(HINSTANCE hinstDLL,ULONG fdwReason,LPVOID lpvReserved) +{ + hInstance = hinstDLL; // Hang on to this DLL's instance handle. + + if (!controlsInit) { + controlsInit = TRUE; + InitCustomControls(hInstance); // Initialize MAX's custom controls + InitCommonControls(); // Initialize Win95 controls + } + + return (TRUE); +} + +// This function returns a string that describes the DLL and where the user +// could purchase the DLL if they don't have it. +__declspec( dllexport ) const TCHAR* LibDescription() +{ + return GetString(IDS_LIBDESCRIPTION); +} + +// This function returns the number of plug-in classes this DLL +//TODO: Must change this number when adding a new class +__declspec( dllexport ) int LibNumberClasses() +{ + return 1; +} + +// This function returns the number of plug-in classes this DLL +__declspec( dllexport ) ClassDesc* LibClassDesc(int i) +{ + switch(i) { + case 0: return GetMaxNifImportDesc(); + default: return 0; + } +} + +// This function returns a pre-defined constant indicating the version of +// the system under which it was compiled. It is used to allow the system +// to catch obsolete DLLs. +__declspec( dllexport ) ULONG LibVersion() +{ + return VERSION_3DSMAX; +} + +TCHAR *GetString(int id) +{ + static TCHAR buf[256]; + + if (hInstance) + return LoadString(hInstance, id, buf, sizeof(buf)) ? buf : NULL; + return NULL; +} + diff --git a/MaxNifImport/MaxNifImport.cpp b/MaxNifImport/MaxNifImport.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9c7022fb287d3669c4c24bb4e0af9ce2f81db9e2 --- /dev/null +++ b/MaxNifImport/MaxNifImport.cpp @@ -0,0 +1,1229 @@ +/********************************************************************** + *< + FILE: MaxNifImport.cpp + + DESCRIPTION: Appwizard generated plugin + + CREATED BY: tazpn (Theo) + + HISTORY: + + *> Copyright (c) 2006, All Rights Reserved. + **********************************************************************/ +#include "stdafx.h" +#include <math.h> +#include <io.h> +#include <sstream> +#include "MaxNifImport.h" +#include <cs/Biped8Api.h> +#include <scenetraversal.h> +#include <plugapi.h> +#include <triobj.h> +#include <bitmap.h> +#include <modstack.h> +#include <iskin.h> +#include <strclass.h> +#include "objectParams.h" +#undef ALPHA_NONE + +#include <math.h> +#include <string.h> + +#include "niflib.h" +#include "obj\NiObject.h" +#include "obj\NiNode.h" +#include "obj\NiTriShape.h" +#include "obj\NiTriShapeData.h" +#include "obj\NiTriStrips.h" +#include "obj\NiTriStripsData.h" +#include "obj\NiMaterialProperty.h" +#include "obj\NiTexturingProperty.h" +#include "obj\NiSourceTexture.h" +#include "obj\NiExtraData.h" +#include "obj\BSBound.h" +#include "obj\NiSkinData.h" +#include "obj\NiSkinInstance.h" +#include "obj\NiSkinPartition.h" + +#include "niutils.h" +#include "AppSettings.h" + +#ifndef ASSERT +#ifdef _DEBUG +#include <crtdbg.h> +#define ASSERT _ASSERTE +#else +#define ASSERT(exp) +#endif +#endif + +using namespace Niflib; + +#define MaxNifImport_CLASS_ID Class_ID(0x794ac1c1, 0x8b4c64c7) + +// Define the standard section names used in the ini file +LPCTSTR NifImportSection = TEXT("MaxNifImport"); +LPCTSTR SystemSection = TEXT("System"); +LPCTSTR BipedImportSection = TEXT("BipedImport"); + +struct NiNodeNameEquivalence : public NumericStringEquivalence +{ + bool operator()(const NiNodeRef& n1, const NiNodeRef& n2) const { + return NumericStringEquivalence::operator()(n1->GetName(), n2->GetName()); + } +}; + +// NIF Importer +class NifImporter +{ +public: + string name; + string path; + ImpInterface *i; + Interface *gi; + BOOL suppressPrompts; + bool iniFileValid; + string iniFileName; + AppSettings *appSettings; + + // Ini settings + bool showTextures; // show textures in viewport + bool removeIllegalFaces; + bool removeDegenerateFaces; + bool enableAutoSmooth; + float autoSmoothAngle; + bool flipUVTextures; + + // Biped/Bones related settings + string skeleton; + float bipedHeight; + string skeletonCheck; + float bipedAngle; + float bipedAnkleAttach; + bool bipedTrianglePelvis; + bool importSkeleton; + bool useBiped; + bool hasSkeleton; + bool isBiped; + bool removeUnusedImportedBones; + + vector<NiObjectRef> blocks; + vector<NiNodeRef> nodes; + + NifImporter(const TCHAR *Name,ImpInterface *I,Interface *GI, BOOL SuppressPrompts) + : name(Name), i(I), gi(GI), suppressPrompts(SuppressPrompts) + { + char buffer[MAX_PATH] = {0}, *p = NULL; + GetFullPathName(Name, _countof(buffer), buffer, &p); + if (p) *p = 0; + path = buffer; + + blocks = ReadNifList( name ); + nodes = DynamicCast<NiNode>(blocks); + iniFileValid = false; + + if (isValid()) { + LoadIniSettings(); + } + } + bool isValid() const { return (0 != blocks.size()); } + + // Ini File related routines + void LoadIniSettings(); + void SaveIniSettings(); + + // Generic IniFile reading routine + template<typename T> + T GetIniValue(LPCTSTR Section, LPCTSTR Setting, T Default){ + if (!iniFileValid) + return Default; + return ::GetIniValue<T>(Section, Setting, Default, iniFileName.c_str()); + } + // Generic IniFile reading routine + template<typename T> + void SetIniValue(LPCTSTR Section, LPCTSTR Setting, T value){ + if (!iniFileValid) + return; + ::SetIniValue<T>(Section, Setting, value, iniFileName.c_str()); + } + + bool HasSkeleton(); + bool IsBiped(); + void ImportBones(vector<NiNodeRef>& bones); + void ImportBones(NiNodeRef blocks); + void ImportBipeds(vector<NiNodeRef>& blocks); + void AlignBiped(IBipMaster* master, NiNodeRef block); + void PositionBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false); + void RotateBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false); + void ScaleBiped(IBipMaster* master, NiNodeRef block, bool Recurse = false); + bool ImportMeshes(NiNodeRef block); + string FindImage(const string& name); + + void SetTrangles(Mesh& mesh, vector<Triangle>& v, bool hasTexture); + bool ImportMesh(NiTriShapeRef triShape); + bool ImportMesh(NiTriStripsRef triStrips); + bool ImportMaterialAndTextures(ImpNode *node, NiAVObjectRef avObject); + bool ImportTransform(ImpNode *node, NiAVObjectRef avObject); + bool ImportMesh(ImpNode *node, TriObject *o, NiTriBasedGeomRef triGeom, NiTriBasedGeomDataRef triGeomData, bool hasTexture); + + bool ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom); + Texmap* CreateTexture(TexDesc& desc); +}; + + + + +class MaxNifImport : public SceneImport { + public: + + static HWND hParams; + + int ExtCount(); // Number of extensions supported + const TCHAR * Ext(int n); // Extension #n (i.e. "3DS") + const TCHAR * LongDesc(); // Long ASCII description (i.e. "Autodesk 3D Studio File") + const TCHAR * ShortDesc(); // Short ASCII description (i.e. "3D Studio") + const TCHAR * AuthorName(); // ASCII Author name + const TCHAR * CopyrightMessage(); // ASCII Copyright message + const TCHAR * OtherMessage1(); // Other message #1 + const TCHAR * OtherMessage2(); // Other message #2 + unsigned int Version(); // Version number * 100 (i.e. v3.01 = 301) + void ShowAbout(HWND hWnd); // Show DLL's "About..." box + int DoImport(const TCHAR *name,ImpInterface *i,Interface *gi, BOOL suppressPrompts=FALSE); // Import file + + //Constructor/Destructor + MaxNifImport(); + ~MaxNifImport(); + + string iniFileName; + string shortDescription; +}; + + + +class MaxNifImportClassDesc : public ClassDesc2 { + public: + int IsPublic() { return TRUE; } + void * Create(BOOL loading = FALSE) { return new MaxNifImport(); } + const TCHAR * ClassName() { return GetString(IDS_CLASS_NAME); } + SClass_ID SuperClassID() { return SCENE_IMPORT_CLASS_ID; } + Class_ID ClassID() { return MaxNifImport_CLASS_ID; } + const TCHAR* Category() { return GetString(IDS_CATEGORY); } + + const TCHAR* InternalName() { return _T("MaxNifImport"); } // returns fixed parsable name (scripter-visible name) + HINSTANCE HInstance() { return hInstance; } // returns owning module handle + + +}; + +static MaxNifImportClassDesc MaxNifImportDesc; +ClassDesc2* GetMaxNifImportDesc() { return &MaxNifImportDesc; } + +BOOL CALLBACK MaxNifImportOptionsDlgProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam) { + static NifImporter *imp = NULL; + + switch(message) { + case WM_INITDIALOG: + { + imp = (NifImporter *)lParam; + CenterWindow(hWnd,GetParent(hWnd)); + + char buffer[33] = {0}; + sprintf(buffer, "%f", imp->bipedHeight); + SetWindowText(GetDlgItem(hWnd, IDC_EDITHEIGHT), buffer); + } + return TRUE; + + case WM_CLOSE: + { + char buffer[33] = {0}, *end = NULL; + GetWindowText(GetDlgItem(hWnd, IDC_EDITHEIGHT), buffer, 33); + imp->bipedHeight = strtod(buffer, &end); + EndDialog(hWnd, 0); + } + return TRUE; + + case WM_COMMAND : + { + switch (wParam) + { + case IDCLOSE : + SendMessage(hWnd, WM_CLOSE, 0, 0); + return TRUE; + } + } + break; + } + return FALSE; +} + + +//--- MaxNifImport ------------------------------------------------------- +MaxNifImport::MaxNifImport() +{ + Interface *gi = GetCOREInterface(); + TCHAR iniName[MAX_PATH]; + if (gi) { + LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR); + PathCombine(iniName, pluginDir, "MaxNifTools.ini"); + } else { + GetModuleFileName(NULL, iniName, _countof(iniName)); + if (LPTSTR fname = PathFindFileName(iniName)) + fname = NULL; + PathAddBackslash(iniName); + PathAppend(iniName, "plugcfg"); + PathAppend(iniName, "MaxNifTools.ini"); + } + iniFileName = iniName; + shortDescription = GetIniValue<string>(SystemSection, "ShortDescription", "Netimmerse/Gamebryo", iniFileName.c_str()); +} + +MaxNifImport::~MaxNifImport() +{ + +} + +int MaxNifImport::ExtCount() +{ + //TODO: Returns the number of file name extensions supported by the plug-in. + return 1; +} + +const TCHAR *MaxNifImport::Ext(int n) +{ + //TODO: Return the 'i-th' file name extension (i.e. "3DS"). + return _T("NIF"); +} + +const TCHAR *MaxNifImport::LongDesc() +{ + //TODO: Return long ASCII description (i.e. "Targa 2.0 Image File") + return _T("Netimmerse/Gamebryo"); +} + +const TCHAR *MaxNifImport::ShortDesc() +{ + //TODO: Return short ASCII description (i.e. "Targa") + return shortDescription.c_str(); +} + +const TCHAR *MaxNifImport::AuthorName() +{ + //TODO: Return ASCII Author name + return _T("Theo"); +} + +const TCHAR *MaxNifImport::CopyrightMessage() +{ + // Return ASCII Copyright message + return _T( + "Copyright (c) 2006, NIF File Format Library and Tools\n" + "All rights reserved." + ); +} + +const TCHAR *MaxNifImport::OtherMessage1() +{ + return _T( + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS\n" + "\"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT\n" + "LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS\n" + "FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE\n" + "COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,\n" + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n" + "BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n" + "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n" + "CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\n" + "LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\n" + "ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\n" + "POSSIBILITY OF SUCH DAMAGE.\n" + ); +} + +const TCHAR *MaxNifImport::OtherMessage2() +{ + //TODO: Return other message #2 in any + return _T(""); +} + +unsigned int MaxNifImport::Version() +{ + //TODO: Return Version number * 100 (i.e. v3.01 = 301) + return 1 * 100; +} + +void MaxNifImport::ShowAbout(HWND hWnd) +{ + // Optional +} + + +int MaxNifImport::DoImport(const TCHAR *filename,ImpInterface *i, Interface *gi, BOOL suppressPrompts) +{ + bool ok = true; + try + { + HoldSuspend myHold(TRUE); + + AppSettings::Initialize(gi); + + std::string current_file = filename; + + NifImporter importer(filename, i, gi, suppressPrompts); + if (!importer.isValid()) + return FALSE; + + //if(!suppressPrompts) + // DialogBoxParam(hInstance, + // MAKEINTRESOURCE(IDD_PANEL), + // GetActiveWindow(), + // MaxNifImportOptionsDlgProc, (LPARAM)&importer); + + // + if (importer.isBiped) + { + if (importer.useBiped){ + importer.ImportBipeds(importer.nodes); + } else { + importer.ImportBones(DynamicCast<NiNode>(importer.nodes[0]->GetChildren())); + } + } + else + { + vector<string> importedBones; + if (importer.importSkeleton && !importer.skeleton.empty()) + { + NifImporter skelImport(importer.skeleton.c_str(), i, gi, suppressPrompts); + if (skelImport.isValid()) + { + if (skelImport.useBiped){ + skelImport.ImportBipeds(skelImport.nodes); + } else { + skelImport.ImportBones(DynamicCast<NiNode>(skelImport.nodes[0]->GetChildren())); + if (importer.removeUnusedImportedBones){ + importedBones = GetNamesOfNodes(skelImport.nodes); + } + } + } + } + if (importer.isValid()) { + + importer.ImportBones(DynamicCast<NiNode>(importer.nodes[0]->GetChildren())); + ok = importer.ImportMeshes(importer.nodes[0]); + if (importer.removeUnusedImportedBones){ + vector<string> importedNodes = GetNamesOfNodes(importer.nodes); + sort(importedBones.begin(), importedBones.end()); + sort(importedNodes.begin(), importedNodes.end()); + vector<string> results; + results.resize(importedBones.size()); + vector<string>::iterator end = set_difference ( + importedBones.begin(), importedBones.end(), + importedNodes.begin(), importedNodes.end(), results.begin()); + for (vector<string>::iterator itr = results.begin(); itr != end; ++itr){ + if (INode *node = gi->GetINodeByName((*itr).c_str())){ + node->Delete(0, TRUE); + } + } + } + } + } + } + catch( exception & e ) + { + e=e; + ok = false; + } + catch( exception * e ) + { + e=e; + ok = false; + } + catch( ... ) + { + ok = false; + } + return ok ? TRUE : FALSE; +} + +void NifImporter::LoadIniSettings() +{ + TCHAR iniName[MAX_PATH]; + LPCTSTR pluginDir = gi->GetDir(APP_PLUGCFG_DIR); + PathCombine(iniName, pluginDir, "MaxNifTools.ini"); + this->iniFileName = iniName; + iniFileValid = (-1 != _access(iniName, 0)); + + string curapp = GetIniValue<string>(NifImportSection, "CurrentApp", ""); + AppSettingsMap::iterator itr = TheAppSettings.find(curapp); + appSettings = (itr != TheAppSettings.end()) ? &(*itr).second : NULL; + + useBiped = GetIniValue<bool>(NifImportSection, "UseBiped", false); + skeletonCheck = GetIniValue<string>(NifImportSection, "SkeletonCheck", "Bip*"); + showTextures = GetIniValue<bool>(NifImportSection, "ShowTextures", true); + + removeIllegalFaces = GetIniValue<bool>(NifImportSection, "RemoveIllegalFaces", true); + removeDegenerateFaces = GetIniValue<bool>(NifImportSection, "RemoveDegenerateFaces", true); + enableAutoSmooth = GetIniValue<bool>(NifImportSection, "EnableAutoSmooth", true); + autoSmoothAngle = GetIniValue<float>(NifImportSection, "AutoSmoothAngle", 30.0f); + flipUVTextures = GetIniValue<bool>(NifImportSection, "FlipUVTextures", true); + + skeleton = (appSettings != NULL) ? appSettings->Skeleton : ""; + + bipedHeight = GetIniValue<float>(BipedImportSection, "BipedHeight", 131.90f); + bipedAngle = GetIniValue<float>(BipedImportSection, "BipedAngle", 90.0f); + bipedAnkleAttach = GetIniValue<float>(BipedImportSection, "BipedAnkleAttach", 0.2f); + bipedTrianglePelvis = GetIniValue<bool>(BipedImportSection, "BipedTrianglePelvis", false); + removeUnusedImportedBones = GetIniValue<bool>(BipedImportSection, "RemoveUnusedImportedBones", false); + + importSkeleton = hasSkeleton = HasSkeleton(); + isBiped = IsBiped(); +} + +void NifImporter::SaveIniSettings() +{ + SetIniValue<bool>(NifImportSection, "UseBiped", useBiped); + SetIniValue<string>(NifImportSection, "Skeleton", skeleton); + SetIniValue<string>(NifImportSection, "SkeletonCheck", skeletonCheck); + + SetIniValue<float>(BipedImportSection, "BipedHeight", bipedHeight); + SetIniValue<float>(BipedImportSection, "BipedAngle", bipedAngle); + SetIniValue<float>(BipedImportSection, "BipedAnkleAttach", bipedAnkleAttach); + SetIniValue<bool>(BipedImportSection, "BipedTrianglePelvis", bipedTrianglePelvis); +} + +bool NifImporter::HasSkeleton() +{ + if (!skeletonCheck.empty()){ + vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, skeletonCheck.c_str()); + return !bipedRoots.empty(); + } + return false; +} + +bool NifImporter::IsBiped() +{ + if (hasSkeleton){ + list<NiExtraDataRef> extraData = nodes[0]->GetExtraData(); + if (!extraData.empty()) { + return ( DynamicCast<BSBound>(extraData).size() != 0 ); + } + } + return false; +} + +void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes) +{ + IBipMaster* master = NULL; + try + { + vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, "Bip??"); + std::stable_sort(bipedRoots.begin(), bipedRoots.end(), NiNodeNameEquivalence()); + for (vector<NiNodeRef>::iterator bipedItr = bipedRoots.begin(); bipedItr != bipedRoots.end(); ++bipedItr) + { + string bipname = (*bipedItr)->GetName(); + string match = bipname + "*"; + vector<NiNodeRef> bipedNodes = SelectNodesByName(nodes, match.c_str()); + + float height = this->bipedHeight; +#if USE_CUSTOM_BSBOUND + list<NiExtraDataRef> extraData = nodes[0]->GetExtraData(); + if (!extraData.empty()) { + BSBoundRef bound = SelectFirst<BSBound>(extraData); + if (bound) { + array<float,6> floats = bound->GetUnknownFloats(); + height = floats[2] * 2.0f; + } + } +#endif + float angle = TORAD(bipedAngle); + Point3 wpos(0.0f,0.0f,0.0f); + BOOL arms = (CountNodesByName(bipedNodes, FormatText("%s L UpperArm", bipname.c_str())) > 0) ? TRUE : FALSE; + BOOL triPelvis = bipedTrianglePelvis ? TRUE : FALSE; + int nnecklinks=CountNodesByName(bipedNodes, FormatText("%s Neck*", bipname.c_str())); + int nspinelinks=CountNodesByName(bipedNodes, FormatText("%s Spine*", bipname.c_str())); + int nleglinks = 3 + CountNodesByName(bipedNodes, FormatText("%s L HorseLink", bipname.c_str())); + int ntaillinks = CountNodesByName(bipedNodes, FormatText("%s Tail*", bipname.c_str())); + int npony1links = CountNodesByName(bipedNodes, FormatText("%s Ponytail1*", bipname.c_str())); + int npony2links = CountNodesByName(bipedNodes, FormatText("%s Ponytail2*", bipname.c_str())); + int numfingers = CountNodesByName(bipedNodes, FormatText("%s L Finger?", bipname.c_str())); + int nfinglinks = CountNodesByName(bipedNodes, FormatText("%s L Finger0*", bipname.c_str())); + int numtoes = CountNodesByName(bipedNodes, FormatText("%s L Toe?", bipname.c_str())); + int ntoelinks = CountNodesByName(bipedNodes, FormatText("%s L Toe0*", bipname.c_str())); + BOOL prop1exists = CountNodesByName(bipedNodes, FormatText("%s Prop1", bipname.c_str())) ? TRUE : FALSE; + BOOL prop2exists = CountNodesByName(bipedNodes, FormatText("%s Prop2", bipname.c_str())) ? TRUE : FALSE; + BOOL prop3exists = CountNodesByName(bipedNodes, FormatText("%s Prop3", bipname.c_str())) ? TRUE : FALSE; + int forearmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Fore*Twist*", bipname.c_str())); + int upperarmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Up*Twist*", bipname.c_str())); + int thighTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Thigh*Twist*", bipname.c_str())); + int calfTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Calf*Twist*", bipname.c_str())); + int horseTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Horse*Twist*", bipname.c_str())); + + NiNodeRef root = nodes[0]; + IBipMaster* master = CreateNewBiped(height, angle, wpos, arms, triPelvis, nnecklinks, nspinelinks, + nleglinks, ntaillinks, npony1links, npony2links, numfingers, nfinglinks, numtoes, + ntoelinks, bipedAnkleAttach, prop1exists, prop2exists, prop3exists, + forearmTwistLinks, upperarmTwistLinks, thighTwistLinks, + calfTwistLinks, horseTwistLinks); + master->SetRootName(const_cast<TCHAR*>(bipname.c_str())); + if (master) + { + master->BeginModes(BMODE_FIGURE, FALSE); + master->SetTrianglePelvis(FALSE); + master->SetDisplaySettings(BDISP_BONES); + LPCTSTR bipname = master->GetRootName(); + + // Rename twists, if necessary + RenameNode(gi, FormatText("%s L ForeTwist", bipname), FormatText("%s L ForearmTwist", bipname)); + RenameNode(gi, FormatText("%s R ForeTwist", bipname), FormatText("%s R ForearmTwist", bipname)); + RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s L UpperArmTwist", bipname)); + RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s R UpperArmTwist", bipname)); + + NiNodeRef nifBip = FindNodeByName(nodes, bipname); + //AlignBiped(master, nifBip); + ScaleBiped(master, nifBip, true); + PositionBiped(master, nifBip, true); + RotateBiped(master, nifBip, true); + } + } + } + catch( exception & e ) + { + e=e; + } + catch( ... ) + { + } + if (master) + master->EndModes(BMODE_FIGURE, TRUE); +} + +void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef block) +{ + string name = block->GetName(); + Matrix44 wt = block->GetWorldTransform(); + Matrix44 lt = block->GetLocalTransform(); + + Vector3 pos; Matrix33 rot; float scale; + wt.Decompose(pos, rot, scale); + + INode *node = gi->GetINodeByName(name.c_str()); + if (node != NULL) + { + Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m + master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + Matrix3 im = Inverse(m); + + Point3 p; Quat q; Point3 s; + DecomposeMatrix(im, p, q, s); + master->SetBipedRot(q, 0, node, TRUE); + } + else + { + + } + vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); + for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ + AlignBiped(master, *itr); + } +} + +void NifImporter::PositionBiped(IBipMaster* master, NiNodeRef block, bool Recurse) +{ + string name = block->GetName(); + + Matrix44 wt = block->GetWorldTransform(); + Matrix44 lt = block->GetLocalTransform(); + + Vector3 pos; Matrix33 rot; float scale; + wt.Decompose(pos, rot, scale); + + INode *node = gi->GetINodeByName(name.c_str()); + if (node != NULL) + { + Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m + master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + Matrix3 im = Inverse(m); + Point3 p; Quat q; Point3 s; + DecomposeMatrix(im, p, q, s); + master->SetBipedRot(q, 0, node, TRUE); + } + else + { + + } + if (Recurse) + { + vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); + for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ + PositionBiped(master, *itr, Recurse); + } + } +} + +void NifImporter::RotateBiped(IBipMaster* master, NiNodeRef block, bool Recurse) +{ + string name = block->GetName(); + + Matrix44 wt = block->GetWorldTransform(); + Matrix44 lt = block->GetLocalTransform(); + + Vector3 pos; Matrix33 rot; float scale; + wt.Decompose(pos, rot, scale); + + INode *node = gi->GetINodeByName(name.c_str()); + if (node != NULL) + { + Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m + master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + Matrix3 im = Inverse(m); + + Point3 p; Quat q; Point3 s; + DecomposeMatrix(im, p, q, s); + master->SetBipedRot(q, 0, node, TRUE); + } + else + { + + } + if (Recurse) + { + vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); + for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ + RotateBiped(master, *itr, Recurse); + } + } +} + +void NifImporter::ScaleBiped(IBipMaster* master, NiNodeRef block, bool Recurse) +{ + string name = block->GetName(); + + Matrix44 wt = block->GetWorldTransform(); + Matrix44 lt = block->GetLocalTransform(); + + Vector3 pos; Matrix33 rot; float scale; + wt.Decompose(pos, rot, scale); + + INode *node = gi->GetINodeByName(name.c_str()); + if (node != NULL) + { + Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m + master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + Matrix3 im = Inverse(m); + + Point3 p; Quat q; Point3 s; + DecomposeMatrix(im, p, q, s); + master->SetBipedRot(q, 0, node, TRUE); + } + else + { + + } + if (Recurse) + { + vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); + for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ + ScaleBiped(master, *itr, Recurse); + } + } +} + + +INode *CreateBone(const string& name, Point3 startPos, Point3 endPos, Point3 zAxis) +{ + if (FPInterface * fpBones = GetCOREInterface(Interface_ID(0x438aff72, 0xef9675ac))) + { + FunctionID createBoneID = fpBones->FindFn(TEXT("createBone")); + FPValue result; + FPParams params (3, TYPE_POINT3, &startPos, TYPE_POINT3, &endPos, TYPE_POINT3, &zAxis); + FPStatus status = fpBones->Invoke(createBoneID, result, ¶ms); + if (status == FPS_OK && result.type == TYPE_INODE) + { + if (INode *n = result.n) + { + n->SetName(const_cast<TCHAR*>(name.c_str())); + + const float wmin = 0.5f; + const float wmax = 3.0f; + float len = fabs(Length(startPos)-Length(endPos)); + float width = max(wmin, min(wmax, len / 4.0f)); + + if (Object* o = n->GetObjectRef()) + { + setMAXScriptValue(o->GetReference(0), "width", 0, width); + setMAXScriptValue(o->GetReference(0), "height", 0, width); + } + } + return result.n; + } + } + return NULL; +} + +void NifImporter::ImportBones(vector<NiNodeRef>& bones) +{ + for (vector<NiNodeRef>::iterator itr = bones.begin(), end = bones.end(); itr != end; ++itr){ + ImportBones(*itr); + } +} + +void NifImporter::ImportBones(NiNodeRef node) +{ + try + { + string name = node->GetName(); + vector<NiNodeRef> children = DynamicCast<NiNode>(node->GetChildren()); + NiNodeRef parent = node->GetParent(); + + Matrix3 im = TOMATRIX3(node->GetWorldTransform(), true); + Point3 p = im.GetTrans(); + Quat q(im); + q.Normalize(); + INode *bone = gi->GetINodeByName(name.c_str()); + if (bone) + { + // Is there a better way of "Affect Pivot Only" behaviors? + INode *pinode = bone->GetParentNode(); + if (pinode) + bone->Detach(0,1); + PositionAndRotateNode(bone, p, q); + if (pinode) + pinode->AttachChild(bone, 1); + } + else + { + Vector3 ppos; + if (!children.empty()) { + for (vector<NiNodeRef>::iterator itr=children.begin(), end = children.end(); itr != end; ++itr) { + Matrix44 cwt = (*itr)->GetWorldTransform(); + Vector3 cpos; Matrix33 crot; float cscale; + cwt.Decompose(cpos, crot, cscale); + ppos += cpos; + } + ppos /= children.size(); + } + else if (parent) + { + Matrix44 pwt = parent->GetWorldTransform(); + Matrix33 prot; float pscale; + pwt.Decompose(ppos, prot, pscale); + } + Point3 pp(ppos.x, ppos.y, ppos.z); + Point3 zAxis(0,1,0); + if (bone = CreateBone(name, p, pp, zAxis)) + { + PositionAndRotateNode(bone, p, q); + if (parent) + { + if (INode *pn = gi->GetINodeByName(parent->GetName().c_str())) + pn->AttachChild(bone, 1); + } + } + } + if (bone) + { + ImportBones(children); + } + } + catch( exception & e ) + { + e=e; + } + catch( ... ) + { + } +} + +bool NifImporter::ImportMeshes(NiNodeRef node) +{ + bool ok = true; + try + { + vector<NiTriShapeRef> trinodes = DynamicCast<NiTriShape>(node->GetChildren()); + for (vector<NiTriShapeRef>::iterator itr = trinodes.begin(), end = trinodes.end(); ok && itr != end; ++itr){ + ok |= ImportMesh(*itr); + } + vector<NiTriStripsRef> tristrips = DynamicCast<NiTriStrips>(node->GetChildren()); + for (vector<NiTriStripsRef>::iterator itr = tristrips.begin(), end = tristrips.end(); ok && itr != end; ++itr){ + ok |= ImportMesh(*itr); + } + vector<NiNodeRef> nodes = DynamicCast<NiNode>(node->GetChildren()); + for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ + ok |= ImportMeshes(*itr); + } + } + catch( exception & e ) + { + e=e; + ok = false; + } + catch( ... ) + { + ok = false; + } + return ok; +} + +Texmap* NifImporter::CreateTexture(TexDesc& desc) +{ + BitmapManager *bmpMgr = TheManager; + if (NiSourceTextureRef texSrc = desc.source){ + string filename = texSrc->GetExternalFileName(); + if (bmpMgr->CanImport(filename.c_str())){ + BitmapTex *bmpTex = NewDefaultBitmapTex(); + bmpTex->SetName(texSrc->GetName().c_str()); + bmpTex->SetMapName(const_cast<TCHAR*>(FindImage(filename).c_str())); + bmpTex->SetAlphaAsMono(TRUE); + bmpTex->SetAlphaSource(ALPHA_NONE); + return bmpTex; + } + } + return NULL; +} + +bool NifImporter::ImportMaterialAndTextures(ImpNode *node, NiAVObjectRef avObject) +{ + // Texture + NiTexturingPropertyRef texRef = avObject->GetPropertyByType( NiTexturingProperty::TypeConst() ); + NiMaterialPropertyRef matRef = avObject->GetPropertyByType( NiMaterialProperty::TypeConst() ); + + bool hasTexture = (texRef && matRef); + if (hasTexture){ + StdMat2 *m = NewDefaultStdMat(); + m->SetName(matRef->GetName().c_str()); + if (showTextures) + m->SetMtlFlag(MTL_DISPLAY_ENABLE_FLAGS, TRUE); + m->SetAmbient(TOCOLOR(matRef->GetAmbientColor()),0); + m->SetDiffuse(TOCOLOR(matRef->GetDiffuseColor()),0); + m->SetSpecular(TOCOLOR(matRef->GetSpecularColor()),0); + Color c = TOCOLOR(matRef->GetEmissiveColor()); + if (c.r != 0 || c.b != 0 || c.g != 0) { + m->SetSelfIllumColorOn(TRUE); + m->SetSelfIllumColor(c,0); + } + m->SetShinStr(0.0,0); + m->SetShininess(matRef->GetGlossiness()/100.0,0); + m->SetOpacity(matRef->GetTransparency(),0); + + // Handle Base/Detail ??? + if (texRef->HasTexture(DECAL_0_MAP)){ + if (Texmap* tex = CreateTexture(texRef->GetTexture(DECAL_0_MAP))) + m->SetSubTexmap(ID_DI, tex); + if (texRef->HasTexture(BASE_MAP)){ + m->LockAmbDiffTex(FALSE); + if (Texmap* tex = CreateTexture(texRef->GetTexture(BASE_MAP))) + m->SetSubTexmap(ID_AM, tex); + } + } else if (texRef->HasTexture(BASE_MAP)) { + if (Texmap* tex = CreateTexture(texRef->GetTexture(BASE_MAP))) + m->SetSubTexmap(ID_DI, tex); + } + // Handle Bump map + if (texRef->HasTexture(BUMP_MAP)) { + if (Texmap* tex = CreateTexture(texRef->GetTexture(BUMP_MAP))) + m->SetSubTexmap(ID_BU, tex); + } + // Shiny map + if (texRef->HasTexture(GLOSS_MAP)) { + if (Texmap* tex = CreateTexture(texRef->GetTexture(GLOSS_MAP))) + m->SetSubTexmap(ID_SS, tex); + } + + // Self illumination + if (texRef->HasTexture(GLOW_MAP)) { + if (Texmap* tex = CreateTexture(texRef->GetTexture(GLOW_MAP))) + m->SetSubTexmap(ID_SI, tex); + } + + gi->GetMaterialLibrary().Add(m); + node->GetINode()->SetMtl(m); + } + return hasTexture; +} + +bool NifImporter::ImportTransform(ImpNode *node, NiAVObjectRef avObject) +{ + Matrix44 wt = avObject->GetWorldTransform(); + + Vector3 pos; Matrix33 rot; float scale; + wt.Decompose(pos, rot, scale); + Point3 p(pos.x, pos.y, pos.z); + + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + m.SetTrans(p); + node->SetTransform(0,m); + return true; +} + +bool NifImporter::ImportMesh(ImpNode *node, TriObject *o, NiTriBasedGeomRef triGeom, NiTriBasedGeomDataRef triGeomData, bool hasTexture) +{ + Mesh& mesh = o->GetMesh(); + + // Vertex info + { + int nVertices = triGeomData->GetVertexCount(); + vector<Vector3> vertices = triGeomData->GetVertices(); + mesh.setNumVerts(nVertices); + for (int i=0; i < nVertices; ++i){ + Vector3 &v = vertices[i]; + mesh.verts[i].Set(v.x, v.y, v.z); + } + } + if (hasTexture) // uv texture info + { + int nUVSet = triGeomData->GetUVSetCount(); + int n = 0; + for (int j=0; j<nUVSet; j++){ + vector<TexCoord> texCoords = triGeomData->GetUVSet(j); + n = texCoords.size(); + mesh.setNumTVerts(n, FALSE); + for (int i=0; i<n; ++i) { + TexCoord& texCoord = texCoords[i]; + mesh.tVerts[i].Set(texCoord.u, (flipUVTextures) ? -texCoord.v : texCoord.v, 0); + } + } + } + if (removeDegenerateFaces) + mesh.RemoveDegenerateFaces(); + if (removeIllegalFaces) + mesh.RemoveIllegalFaces(); + if (enableAutoSmooth) + mesh.AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE); + + // Normals + { + mesh.checkNormals(TRUE); + vector<Vector3> n = triGeomData->GetNormals(); + for (int i=0; i<n.size(); i++){ + Vector3 v = n[i]; + mesh.setNormal(i, Point3(v.x, v.y, v.z)); + } + } + // vertex coloring + { + bool hasAlpha = false; + vector<Color4> cv = triGeomData->GetColors(); + mesh.setNumVertCol(cv.size()); + for (int i=0; i<cv.size(); i++){ + Color4& c = cv[i]; + mesh.vertCol[i].Set(c.r, c.g, c.b); + hasAlpha |= (c.a != 0.0); + } + } + return true; +} + +void NifImporter::SetTrangles(Mesh& mesh, vector<Triangle>& v, bool hasTexture) +{ + int n = v.size(); + mesh.setNumFaces(n); + if (hasTexture) + mesh.setNumTVFaces(n); + for (int i=0; i<n; ++i) { + Triangle& t = v[i]; + Face& f = mesh.faces[i]; + f.setVerts(t.v1, t.v2, t.v3); + f.Show(); + f.setEdgeVisFlags(EDGE_VIS, EDGE_VIS, EDGE_VIS); + if (hasTexture) { + TVFace& tf = mesh.tvFace[i]; + tf.setTVerts(t.v1, t.v2, t.v3); + } + } +} + +bool NifImporter::ImportMesh(NiTriShapeRef triShape) +{ + bool ok = true; + + ImpNode *node = i->CreateNode(); + if(!node) return false; + TriObject *triObject = CreateNewTriObject(); + node->Reference(triObject); + string name = triShape->GetName(); + node->SetName(name.c_str()); + + // Texture + bool hasTexture = ImportMaterialAndTextures(node, triShape); + ImportTransform(node, triShape); + + Mesh& mesh = triObject->GetMesh(); + NiTriShapeDataRef triShapeData = DynamicCast<NiTriShapeData>(triShape->GetData()); + if (triShapeData == NULL) + return false; + + ok |= ImportMesh(node, triObject, triShape, triShapeData, hasTexture); + + // Triangles and texture vertices + if (ok) + { + vector<Triangle> v = triShapeData->GetTriangles(); + SetTrangles(mesh, v, hasTexture); + } + ImportSkin(node, triShape); + i->AddNodeToScene(node); + + if (enableAutoSmooth){ + if (TriObject *tri = GetTriObject(node->GetINode()->GetObjectRef())){ + tri->GetMesh().AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE); + } + } + + return ok; +} + +bool NifImporter::ImportMesh(NiTriStripsRef triStrips) +{ + bool ok = true; + + ImpNode *node = i->CreateNode(); + if(!node) return false; + TriObject *triObject = CreateNewTriObject(); + node->Reference(triObject); + string name = triStrips->GetName(); + node->SetName(name.c_str()); + + // Texture + bool hasTexture = ImportMaterialAndTextures(node, triStrips); + ImportTransform(node, triStrips); + + Mesh& mesh = triObject->GetMesh(); + NiTriStripsDataRef triStripsData = DynamicCast<NiTriStripsData>(triStrips->GetData()); + if (triStripsData == NULL) + return false; + + ok |= ImportMesh(node, triObject, triStrips, triStripsData, hasTexture); + + // Triangles and texture vertices + if (ok) + { + vector<Triangle> v = triStripsData->GetTriangles(); + SetTrangles(mesh, v, hasTexture); + } + ImportSkin(node, triStrips); + i->AddNodeToScene(node); + + // apply autosmooth after object creation for it to take hold + if (enableAutoSmooth){ + if (TriObject *tri = GetTriObject(node->GetINode()->GetObjectRef())){ + tri->GetMesh().AutoSmooth(TORAD(autoSmoothAngle), FALSE, FALSE); + } + } + + return ok; +} + +struct VertexHolder +{ + VertexHolder() : vertIndex(0), count(0) {} + + int vertIndex; + int count; + Tab<INode*> boneNodeList; + Tab<float> weights; +}; + +bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom) +{ + bool ok = true; + NiSkinInstanceRef nifSkin = triGeom->GetSkinInstance(); + if (!nifSkin) + return false; + + INode *tnode = node->GetINode(); + + NiSkinDataRef data = nifSkin->GetSkinData(); + NiSkinPartitionRef part = nifSkin->GetSkinPartition(); + + vector<NiNodeRef> nifBones = nifSkin->GetBones(); + + //create a skin modifier and add it + Modifier *skinMod = GetSkin(tnode); + TriObject *triObject = GetTriObject(tnode->GetObjectRef()); + Mesh& m = triObject->GetMesh(); + + //get the skin interface + ISkin *skin = (ISkin *) skinMod->GetInterface(I_SKIN); + ISkinImportData* iskinImport = (ISkinImportData*) skinMod->GetInterface(I_SKINIMPORTDATA); + + // Create Bone List + Tab<INode*> bones; + int i=0; + for (vector<NiNodeRef>::iterator itr = nifBones.begin(), end = nifBones.end(); itr != end; ++itr, ++i){ + string name = (*itr)->GetName(); + INode *boneRef = gi->GetINodeByName(name.c_str()); + bones.Append(1, &boneRef); + iskinImport->AddBoneEx(boneRef, TRUE); + + // Set Bone Transform + //Matrix3 tm = boneRef->GetObjectTM(0); + //Matrix3 m = TOMATRIX3(data->GetBoneTransform(i)); + //iskinImport->SetBoneTm(boneRef, tm, m); + } + ObjectState os = tnode->EvalWorldState(0); + + // Need to trigger ModifyObject in BonesDefMod prior to adding vertices or nothing is added + GetCOREInterface()->ForceCompleteRedraw(); + + // Need to get a list of bones and weights for each vertex. + vector<VertexHolder> vertexHolders; + vertexHolders.resize(m.numVerts); + for (int i=0, n=data->GetBoneCount();i<n; ++i){ + if (INode *boneRef = bones[i]){ + vector<SkinWeight> weights = data->GetBoneWeights(i); + for (vector<SkinWeight>::iterator itr=weights.begin(), end=weights.end(); itr != end; ++itr){ + VertexHolder& h = vertexHolders[itr->index]; + h.vertIndex = itr->index; + ++h.count; + h.weights.Append(1, &itr->weight); + h.boneNodeList.Append(1, &boneRef); + } + } + } + + // Assign the weights + for (vector<VertexHolder>::iterator itr=vertexHolders.begin(), end=vertexHolders.end(); itr != end; ++itr){ + VertexHolder& h = (*itr); + if (h.count){ + float sum = 0.0f; + for (int i=0; i<h.count; ++i) + sum += h.weights[i]; + ASSERT(fabs(sum-1.0) < 0.001); + BOOL add = iskinImport->AddWeights(tnode, h.vertIndex, h.boneNodeList, h.weights); + add = add; + } + } + return ok; +} + +string NifImporter::FindImage(const string& name) +{ + TCHAR buffer[MAX_PATH]; + + // Simply check for fully qualified path + if (PathIsRoot(name.c_str())) { + if (-1 != _taccess(name.c_str(), 0)) + return string(buffer); + } + if (!path.empty()) { + PathCombine(buffer, path.c_str(), name.c_str()); // try as-is + if (-1 != _taccess(buffer, 0)) + return string(buffer); + + // try only filename in nif directory + PathCombine(buffer, path.c_str(), PathFindFileName(name.c_str())); + if (-1 != _taccess(buffer, 0)) + return string(buffer); + } + if (appSettings != NULL) { + return appSettings->FindImage(name); + } + return name; +} \ No newline at end of file diff --git a/MaxNifImport/MaxNifImport.def b/MaxNifImport/MaxNifImport.def new file mode 100644 index 0000000000000000000000000000000000000000..d9a81096db52297c4fae18e6a43ed316c748dd1f --- /dev/null +++ b/MaxNifImport/MaxNifImport.def @@ -0,0 +1,8 @@ +LIBRARY MaxNifImport.dli +EXPORTS + LibDescription @1 + LibNumberClasses @2 + LibClassDesc @3 + LibVersion @4 +SECTIONS + .data READ WRITE diff --git a/MaxNifImport/MaxNifImport.h b/MaxNifImport/MaxNifImport.h new file mode 100644 index 0000000000000000000000000000000000000000..574cd65752e302dd91004b91711d20d18bdd4ffa --- /dev/null +++ b/MaxNifImport/MaxNifImport.h @@ -0,0 +1,32 @@ +/********************************************************************** + *< + FILE: MaxNifImport.h + + DESCRIPTION: Includes for Plugins + + CREATED BY: tazpn (Theo) + + HISTORY: + + *> Copyright (c) 2006, All Rights Reserved. + **********************************************************************/ + +#ifndef __MaxNifImport__H +#define __MaxNifImport__H + +#include "Max.h" +#include "resource.h" +#include "istdplug.h" +#include "iparamb2.h" +#include "iparamm2.h" +//SIMPLE TYPE + +#include <direct.h> +#include <commdlg.h> + + +extern TCHAR *GetString(int id); + +extern HINSTANCE hInstance; + +#endif diff --git a/MaxNifImport/MaxNifImport.rc b/MaxNifImport/MaxNifImport.rc new file mode 100644 index 0000000000000000000000000000000000000000..9641d48eeef1e3731abf573db57e4bce18241233 --- /dev/null +++ b/MaxNifImport/MaxNifImport.rc @@ -0,0 +1,152 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_PANEL DIALOGEX 0, 0, 142, 59 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOOLWINDOW +FONT 8, "MS Sans Serif", 0, 0, 0x1 +BEGIN + EDITTEXT IDC_EDITHEIGHT,68,15,62,14,ES_AUTOHSCROLL + LTEXT "&Height:",IDC_STATIC,16,18,49,8 + PUSHBUTTON "&Close",IDCLOSE,44,38,50,14 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_PANEL, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 135 + TOPMARGIN, 7 + BOTTOMMARGIN, 52 + END +END +#endif // APSTUDIO_INVOKED + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,0,0,1 + PRODUCTVERSION 1,0,0,1 + FILEFLAGSMASK 0x37L +#ifdef _DEBUG + FILEFLAGS 0x21L +#else + FILEFLAGS 0x20L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", "3ds Max Nif Importer" + VALUE "FileVersion", "0, 1, 0, 1" + VALUE "InternalName", "MaxNifImport.dli" + VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved." + VALUE "OriginalFilename", "MaxNifImport.dli" + VALUE "ProductName", "3ds Max Nif Importer" + VALUE "ProductVersion", "0, 1, 0, 1" + VALUE "SpecialBuild", "Alpha" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_LIBDESCRIPTION "Importer for NIF files" + IDS_CATEGORY "NifTools" + IDS_CLASS_NAME "MaxNifImport" + IDS_PARAMS "Parameters" + IDS_SPIN "Spin" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/MaxNifImport/MaxNifImport.vcproj b/MaxNifImport/MaxNifImport.vcproj new file mode 100644 index 0000000000000000000000000000000000000000..02ae21c7e41ccf5ff6664c8df7c4a2ae35c89f61 --- /dev/null +++ b/MaxNifImport/MaxNifImport.vcproj @@ -0,0 +1,322 @@ +<?xml version="1.0" encoding="Windows-1252"?> +<VisualStudioProject + ProjectType="Visual C++" + Version="8.00" + Name="MaxNifImport" + ProjectGUID="{283FC287-9AF1-4F34-9B74-2376FE3C2FCD}" + RootNamespace="MaxNifImport" + > + <Platforms> + <Platform + Name="Win32" + /> + </Platforms> + <ToolFiles> + </ToolFiles> + <Configurations> + <Configuration + Name="Release|Win32" + OutputDirectory=".\Release" + IntermediateDirectory=".\Release" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + CommandLine="" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="NDEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Release\MaxNifImport.tlb" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/LD " + InlineFunctionExpansion="1" + AdditionalIncludeDirectories="C:\3dsmax8\maxsdk\include" + PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_USE_MATH_DEFINES" + StringPooling="true" + RuntimeLibrary="0" + EnableFunctionLevelLinking="true" + ForceConformanceInForLoopScope="false" + UsePrecompiledHeader="0" + PrecompiledHeaderFile=".\Release\MaxNifImport.pch" + AssemblerListingLocation=".\Release\" + ObjectFile=".\Release\" + ProgramDataBaseFileName=".\Release\" + WarningLevel="3" + SuppressStartupBanner="true" + CompileAs="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="NDEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalOptions="/MACHINE:I386" + AdditionalDependencies="odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib core.lib geom.lib gfx.lib mesh.lib maxutil.lib maxscrpt.lib paramblk2.lib biped.lib bmm.lib niflib.lib" + OutputFile="C:\3dsmax8\plugins\MaxNifImport.dli" + LinkIncremental="1" + SuppressStartupBanner="true" + AdditionalLibraryDirectories="C:\3dsmax8\maxsdk\lib" + ModuleDefinitionFile=".\MaxNifImport.def" + ProgramDatabaseFile=".\Release\MaxNifImport.pdb" + SubSystem="2" + SetChecksum="true" + BaseAddress="0x16860000" + ImportLibrary=".\Release\MaxNifImport.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="if exist "C:\3dsmax8\plugcfg" (
if not exist "C:\3dsmax8\plugcfg\MaxNifTools.ini" (
copy "$(ProjectDir)MaxNifTools.ini" "C:\3dsmax8\plugcfg\MaxNifTools.ini"
)
)
" + /> + </Configuration> + <Configuration + Name="Debug|Win32" + OutputDirectory=".\Debug" + IntermediateDirectory=".\Debug" + ConfigurationType="2" + InheritedPropertySheets="$(VCInstallDir)VCProjectDefaults\UpgradeFromVC71.vsprops" + UseOfMFC="0" + ATLMinimizesCRunTimeLibraryUsage="false" + > + <Tool + Name="VCPreBuildEventTool" + /> + <Tool + Name="VCCustomBuildTool" + /> + <Tool + Name="VCXMLDataGeneratorTool" + /> + <Tool + Name="VCWebServiceProxyGeneratorTool" + /> + <Tool + Name="VCMIDLTool" + PreprocessorDefinitions="_DEBUG" + MkTypLibCompatible="true" + SuppressStartupBanner="true" + TargetEnvironment="1" + TypeLibraryName=".\Debug\MaxNifImport.tlb" + /> + <Tool + Name="VCCLCompilerTool" + AdditionalOptions="/LD " + Optimization="0" + AdditionalIncludeDirectories="" + PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_CRT_SECURE_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_USE_MATH_DEFINES" + GeneratePreprocessedFile="0" + ExceptionHandling="2" + RuntimeLibrary="1" + ForceConformanceInForLoopScope="false" + UsePrecompiledHeader="0" + PrecompiledHeaderFile=".\Debug\MaxNifImport.pch" + AssemblerListingLocation=".\Debug\" + ObjectFile=".\Debug\" + ProgramDataBaseFileName=".\Debug\" + WarningLevel="3" + SuppressStartupBanner="true" + DebugInformationFormat="4" + CompileAs="0" + /> + <Tool + Name="VCManagedResourceCompilerTool" + /> + <Tool + Name="VCResourceCompilerTool" + PreprocessorDefinitions="_DEBUG" + Culture="1033" + /> + <Tool + Name="VCPreLinkEventTool" + /> + <Tool + Name="VCLinkerTool" + AdditionalOptions="/MACHINE:I386" + AdditionalDependencies="odbc32.lib odbccp32.lib comctl32.lib shlwapi.lib core.lib geom.lib gfx.lib mesh.lib maxutil.lib maxscrpt.lib paramblk2.lib biped.lib bmm.lib niflibd.lib" + OutputFile="C:\3dsmax8\plugins\MaxNifImport.dli" + LinkIncremental="2" + SuppressStartupBanner="true" + AdditionalLibraryDirectories="C:\3dsmax8\maxsdk\lib" + IgnoreDefaultLibraryNames="msvcrtd.lib" + ModuleDefinitionFile=".\MaxNifImport.def" + GenerateDebugInformation="true" + ProgramDatabaseFile=".\Debug\MaxNifImport.pdb" + SubSystem="2" + BaseAddress="0x16860000" + ImportLibrary=".\Debug\MaxNifImport.lib" + /> + <Tool + Name="VCALinkTool" + /> + <Tool + Name="VCManifestTool" + /> + <Tool + Name="VCXDCMakeTool" + /> + <Tool + Name="VCBscMakeTool" + /> + <Tool + Name="VCFxCopTool" + /> + <Tool + Name="VCAppVerifierTool" + /> + <Tool + Name="VCWebDeploymentTool" + /> + <Tool + Name="VCPostBuildEventTool" + CommandLine="if exist "C:\3dsmax8\plugcfg" (
if not exist "C:\3dsmax8\plugcfg\MaxNifTools.ini" (
copy "$(ProjectDir)MaxNifTools.ini" "C:\3dsmax8\plugcfg\MaxNifTools.ini"
)
)
" + /> + </Configuration> + </Configurations> + <References> + </References> + <Files> + <Filter + Name="Source Files" + Filter="cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" + > + <File + RelativePath=".\DllEntry.cpp" + > + </File> + <File + RelativePath=".\MaxNifImport.def" + > + </File> + <File + RelativePath=".\stdafx.cpp" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="1" + /> + </FileConfiguration> + </File> + </Filter> + <Filter + Name="Header Files" + Filter="h;hpp;hxx;hm;inl" + > + <File + RelativePath=".\niutils.h" + > + </File> + <File + RelativePath=".\objectParams.h" + > + </File> + <File + RelativePath=".\resource.h" + > + </File> + <File + RelativePath=".\stdafx.h" + > + </File> + </Filter> + <Filter + Name="Resource Files" + Filter="ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" + > + <File + RelativePath=".\MaxNifImport.rc" + > + </File> + </Filter> + <Filter + Name="Importer" + > + <File + RelativePath=".\AppSettings.cpp" + > + </File> + <File + RelativePath=".\AppSettings.h" + > + </File> + <File + RelativePath=".\MaxNifImport.cpp" + > + <FileConfiguration + Name="Debug|Win32" + > + <Tool + Name="VCCLCompilerTool" + UsePrecompiledHeader="2" + /> + </FileConfiguration> + </File> + <File + RelativePath=".\MaxNifImport.h" + > + </File> + <File + RelativePath=".\niutils.cpp" + > + </File> + </Filter> + <File + RelativePath=".\MaxNifImport_Readme.txt" + > + </File> + <File + RelativePath=".\MaxNifTools.ini" + > + </File> + </Files> + <Globals> + </Globals> +</VisualStudioProject> diff --git a/MaxNifImport/MaxNifImport_Readme.txt b/MaxNifImport/MaxNifImport_Readme.txt new file mode 100644 index 0000000000000000000000000000000000000000..6026a04538ca9ba5059cdd9b358bc6bfda71cb06 --- /dev/null +++ b/MaxNifImport/MaxNifImport_Readme.txt @@ -0,0 +1,75 @@ +3ds Max Nif Importer v0.1 + +========================= +ABOUT +========================= +This is an importer for the NIF file format for 3ds Max 8. + +It uses the NIF File Format Library and Tools library. + +See the plugin INI file for advanced configuration. + +========================= +INSTALL +========================= +Extract file to 3dsmax8 installation directory. + +Edit the MaxNifTools.ini file to adjust paths and other settings. + +========================= +REQUIREMENTS +========================= +3ds Max 8 + +========================= +UNINSTALLING +========================= +Delete the installed files from 3dsmax directory. See the list below in the +Installed Files section. + +========================= +CHANGE LIST +========================= +v 0.1 + o Initial Revision + +========================= +KNOWN ISSUES +========================= +None + +========================= +INSTALLED FILES +========================= +MaxNifImport_Readme.txt +plugins\MaxNifImport.dli +plugcfg\MaxNifTools.ini + + +========================= +WEBSITES +========================= +http://niftools.sourceforge.net/forum/index.php +http://niftools.sourceforge.net/forum/viewforum.php?f=6&sid=a7b7b59ba39233760ac22590ea8382e1 + + +========================= +COPYRIGHT +========================= +Copyright (c) 2006, NIF File Format Library and Tools. All rights reserved. + +========================= +LEGAL +========================= +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/MaxNifImport/MaxNifTools.ini b/MaxNifImport/MaxNifTools.ini new file mode 100644 index 0000000000000000000000000000000000000000..71bd9d897f7b5fddebe8b5d96edbc393844a3b9b --- /dev/null +++ b/MaxNifImport/MaxNifTools.ini @@ -0,0 +1,81 @@ +; +; Niftools Max tools configuration file +; +[System] +; ShortDescription used in the +ShortDescription=Netimmerse/Gamebryo + +; KnownApplications - Used to indicate which sections in the ini file point +; to "Applications" which have their own settings in a section below. +KnownApplications=Oblivion;Morrowind;Civ4 + +; Reparse the Applications (and therefore Texture directory cache) on every import/export +Reparse=0 + +[MaxNifImport] +; Current Application to get setting/directory information from. Should match KnownApps above +CurrentApp=Oblivion +; Wildcard to use on all NiNodes to test whether a biped may be used. Default: Bip* +SkeletonCheck=Bip* +; Use Bones or Biped. Biped is broken right now so use bones or nothing. Default: 0 +UseBiped=0 +; Flip the V coordinate. Default: 1 +FlipUVTextures=1 +; Show Textures in the Viewport. Default: 1 +ShowTextures=1 +; Enable AutoSmooth on meshes. Default: 1 +EnableAutoSmooth=1 +; AutoSmooth angle. Default: 30 +AutoSmoothAngle=30.0 +; Remove Double/Illegal faces on meshes on import +RemoveDoubleFaces=1 +RemoveIllegalFaces=1 + +[BipedImport] +; Biped Height. Default: 131.90 +BipedHeight=131.90 +; Biped initial rotation. Default: 90.0 +BipedAngle=90.0 +; Biped Ankle Attach. Default: 0.2 +BipedAnkleAttach=0.2 +; Use Triangle Pelvis. Default: 0 +BipedTrianglePelvis=0 +; Remove unused bones from the biped on import of a mesh. Default: 1 +RemoveUnusedImportedBones=1 + + +;; [Applications] +; TextureRootPaths - Semicolon separated list of base directories to look for files +; TextureExtensions - Extensions to scan for when using TextureSearchPaths +; TextureSeachPaths - Semicolon separated list of directories to look recursively for files in +; Skeleton - Default Skeleton to use when importing oblivion meshes + +[Oblivion] +; Installation Folder +InstallPath=[HKLM\SOFTWARE\Bethesda Softworks\Oblivion]=@"Installed Path" +RootPath=${InstallPath}\Data +ExtractFolder=E:\Nifs\Oblivion +MeshRootPath=${ExtractFolder}\Oblivion - Meshes +TextureRootPath=${ExtractFolder}\Oblivion - Textures - Compressed + +Skeleton=${MeshRootPath}\meshes\characters\_male\skeleton.nif +TextureRootPaths=${RootPath};${TextureRootPath} +TextureExtensions=.dds; +TextureSearchPaths=${RootPath}\Textures;${TextureRootPath}\Textures\Characters;${TextureRootPath}\Textures\Armor + +[Morrowind] +InstallPath=[HKLM\SOFTWARE\Bethesda Softworks\Morrowind]=@"Installed Path" +RootPath=${InstallPath}\Data Files +ExtractFolder=E:\Nifs\Morrowind\Data Files +TextureRootPaths=${RootPath}\Textures;${ExtractFolder}\Textures +TextureExtensions=.tga; +TextureSearchPaths=${RootPath}\Textures + +[Civ4] +InstallPath=[HKEY_LOCAL_MACHINE\SOFTWARE\Firaxis Games\Sid Meier's Civilization 4]=@"INSTALLDIR" +RootPath= +ExtractFolder=C:\Projects\Main\Civilization4\assets +TextureRootPaths=$(ExtractFolder)\art\shared\ +TextureExtensions=.dds;.bmp +TextureSearchPaths= + diff --git a/MaxNifImport/niutils.cpp b/MaxNifImport/niutils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f730127e44d7b23b98b68ce05a07800059866288 --- /dev/null +++ b/MaxNifImport/niutils.cpp @@ -0,0 +1,511 @@ +/********************************************************************** +*< +FILE: NIUtils.cpp + +DESCRIPTION: NifImporter Utilities + +CREATED BY: tazpn (Theo) + +HISTORY: + +*> Copyright (c) 2006, All Rights Reserved. +**********************************************************************/ +#include "stdafx.h" +#include "niutils.h" +#include <string.h> +#include <ctype.h> +#include <locale.h> +#include <malloc.h> +#include <sstream> +#include <modstack.h> +#include <iskin.h> +#include <cs/Biped8Api.h> +#include <cs/OurExp.h> +using namespace std; +using namespace Niflib; + +// Macro to create a dynamically allocated strdup on the stack +#define STRDUPA(p) (_tcscpy((TCHAR*)alloca((_tcslen(p)+1)*sizeof(*p)),p)) + +// sprintf for TSTR without having to worry about buffer size. +TSTR FormatText(const TCHAR* format,...) +{ + TCHAR buffer[512]; + TSTR text; + va_list args; + va_start(args, format); + int nChars = _vsntprintf(buffer, _countof(buffer), format, args); + if (nChars != -1) { + text = buffer; + } else { + size_t Size = _vsctprintf(format, args); + text.Resize(Size); + nChars = _vsntprintf(text.data(), Size, format, args); + } + va_end(args); + return text; +} + +// sprintf for std::string without having to worry about buffer size. +std::string FormatString(const TCHAR* format,...) +{ + TCHAR buffer[512]; + std::string text; + va_list args; + va_start(args, format); + int nChars = _vsntprintf(buffer, _countof(buffer), format, args); + if (nChars != -1) { + text = buffer; + } else { + size_t Size = _vsctprintf(format, args); + TCHAR* pbuf = (TCHAR*)_alloca(Size); + nChars = _vsntprintf(pbuf, Size, format, args); + text = pbuf; + } + va_end(args); + return text; +} + +// Tokenize a string using strtok and return it as a stringlist +stringlist TokenizeString(LPCTSTR str, LPCTSTR delims) +{ + stringlist values; + LPTSTR buf = STRDUPA(str); + for (LPTSTR p = _tcstok(buf, delims); p && *p; p = _tcstok(NULL, delims)){ + values.push_back(string(p)); + } + return values; +} + +// Parse and ini file section and return the results as s NameValueCollection. +NameValueCollection ReadIniSection(LPCTSTR Section, LPCTSTR iniFileName ) +{ + NameValueCollection map; + DWORD len = 2048 * sizeof(TCHAR); + LPTSTR buf = (LPTSTR)calloc(len+2, 1); + while(NULL != buf) { + DWORD rlen = GetPrivateProfileSection(Section, buf, len, iniFileName); + if (rlen != (len-2)) break; + len += 2; + buf = (LPTSTR)realloc(buf, len); + } + if (NULL != buf) { + for (LPTSTR line = buf, next = line + strlen(line) + 1; *line; line = next, next = line + strlen(line) + 1){ + Trim(line); + if (line[0] == ';' || line[0] == 0) + continue; + if (LPTSTR equals = _tcschr(line, TEXT('='))){ + *equals++ = 0; + Trim(line), Trim(equals); + map[string(line)] = string(equals); + } + } + } + return map; +} + +// Expand Qualifiers in string using a ${Name} syntax. Name will be looked up in the +// NameValueCollection and expand in place. Missing names will expand to empty. +string ExpandQualifiers(const string& src, const NameValueCollection& map) +{ + string value; + bool hasPercent = false; + string::size_type end = src.length(); + value.reserve(src.length()); + for (string::size_type i = 0; i < end; ++i) { + if (src[i] == TEXT('$')) { + if (++i < end) { + if (src[i] == TEXT('$')){ + value.append(1, src[i]); + } else if (src[i] == TEXT('{')) { + string::size_type term = src.find_first_of(TEXT('}'), i); + if (term == string::npos) { + i = end; + } else { + string key = src.substr(i+1, term-i-1); + NameValueCollection::const_iterator kvp = map.find(key); + if (kvp != map.end()) { + value.append(kvp->second); + } + i = term; + } + } else if (src[i] == TEXT('(')) { + string::size_type term = src.find_first_of(TEXT(')'), i); + if (term == string::npos) { + i = end; + } else { + string key = src.substr(i+1, term-i-1); + NameValueCollection::const_iterator kvp = map.find(key); + if (kvp != map.end()) { + value.append(kvp->second); + } + i = term; + } + } + } else { + value.append(1, TEXT('$')); + } + } else { + value.append(1, src[i]); + } + } + return value; +} + +// Call ExpandEnvironmentStrings but with std string instead +string ExpandEnvironment(const string& src) +{ + DWORD Size = ExpandEnvironmentStrings(src.c_str(), NULL, 0); + if (TCHAR* pbuf = (TCHAR*)_alloca(Size+2)) { + pbuf[0] = 0; + ExpandEnvironmentStrings(src.c_str(), pbuf, Size+2); + return string(pbuf); + } + return src; +} + + +// Helper struct and method for dealing with standard registry handles +struct str2hdl +{ + const TCHAR *str; + HANDLE key; +}; +const static struct str2hdl RegKeyMap[] = { + {TEXT("HKLM"), HKEY_LOCAL_MACHINE}, + {TEXT("HKCU"), HKEY_CURRENT_USER}, + {TEXT("HKCR"), HKEY_CLASSES_ROOT}, + {TEXT("HKEY_LOCAL_MACHINE"), HKEY_LOCAL_MACHINE}, + {TEXT("HKEY_CURRENT_USER"), HKEY_CURRENT_USER}, + {TEXT("HKEY_CLASSES_ROOT"), HKEY_CLASSES_ROOT}, +}; +static HANDLE GetRegKey(LPCTSTR key) { + for (int i=0; i<_countof(RegKeyMap); ++i) + if (0 == _tcscmp(RegKeyMap[i].str, key)) + return RegKeyMap[i].key; + return 0; +} + +// Returns value from indirect source +// Currently only supports registry string values using '[HKEY\Key]=@"Value"' +// where HKEY is HKLM,HKCU,HKCR +// Key is the registry key to lookup +// Value is the data value to lookup. +string GetIndirectValue(LPCSTR path) +{ + if (!path || !*path) + return string(); + string value; + LPTSTR p = STRDUPA(path); + Trim(p); + if (*p == '['){ + LPTSTR end = _tcschr(++p, ']'); + if (end != NULL) { + *end++ = 0; + // Advance unsafely past unnecessary qualifiers + LPTSTR valueName = end; + end = valueName + strlen(end) - 1; + if (*valueName == '=') ++valueName; + if (*valueName == '@') ++valueName; + if (*valueName == '\"' || *valueName == '\'') ++valueName; + if (*end == '\"' || *end == '\'') *end-- = 0; + Trim(valueName); + + LPTSTR keyEnd = _tcschr(p, '\\'); + if (keyEnd != NULL) { + *keyEnd++ = 0; + HANDLE hRoot = GetRegKey(p); + if (hRoot != 0) { + HKEY hKey = NULL; + if (ERROR_SUCCESS == RegOpenKeyEx((HKEY)hRoot, keyEnd, 0, KEY_READ, &hKey)) { + BYTE buffer[MAX_PATH*sizeof(*path)]; + DWORD dwLen = _countof(buffer); + if (ERROR_SUCCESS == RegQueryValueEx(hKey, valueName, NULL, NULL, (LPBYTE)buffer, &dwLen) && dwLen > 0) { + value = (TCHAR*)buffer; + } + RegCloseKey(hKey); + } + } + } + } + } else { + value = path; + } + return value; +} + +// Original Source: Jack Handy www.codeproject.com +int wildcmp(const TCHAR *wild, const TCHAR *string) { + const TCHAR *cp, *mp; + + while ((*string) && (*wild != '*')) { + if ((*wild != *string) && (*wild != '?')) { + return 0; + } + wild++; + string++; + } + + while (*string) { + if (*wild == '*') { + if (!*++wild) { + return 1; + } + mp = wild; + cp = string+1; + } else if ((*wild == *string) || (*wild == '?')) { + wild++; + string++; + } else { + wild = mp; + string = cp++; + } + } + + while (*wild == '*') { + wild++; + } + return !*wild; +} + +// Same as above but case insensitive +int wildcmpi(const TCHAR *wild, const TCHAR *string) { + const TCHAR *cp, *mp; + int f,l; + while ((*string) && (*wild != '*')) { + f = _totlower( (_TUCHAR)(*string) ); + l = _totlower( (_TUCHAR)(*wild) ); + if ((f != l) && (l != '?')) { + return 0; + } + wild++, string++; + } + while (*string) { + if (*wild == '*') { + if (!*++wild) return 1; + mp = wild, cp = string+1; + } else { + f = _totlower( (_TUCHAR)(*string) ); + l = _totlower( (_TUCHAR)(*wild) ); + if ((f == l) || (l == '?')) { + wild++, string++; + } else { + wild = mp, string = cp++; + } + } + } + while (*wild == '*') wild++; + return !*wild; +} + +//! Renames Max Node if it exists +void RenameNode(Interface *gi, LPCTSTR SrcName, LPCTSTR DstName) +{ + INode *node = gi->GetINodeByName(SrcName); + if (node != NULL) node->SetName(const_cast<LPTSTR>(DstName)); +} + +// Locate a TriObject in an Object if it exists +TriObject* GetTriObject(Object *o) +{ + if (o && o->CanConvertToType(triObjectClassID)) + return (TriObject *)o->ConvertToType(0, triObjectClassID); + while (o->SuperClassID() == GEN_DERIVOB_CLASS_ID && o) + { + IDerivedObject* dobj = (IDerivedObject *)(o); + o = dobj->GetObjRef(); + if (o && o->CanConvertToType(triObjectClassID)) + return (TriObject *)o->ConvertToType(0, triObjectClassID); + } + return NULL; +} + +// Get or Create the Skin Modifier +Modifier *GetSkin(INode *node) +{ + Object* pObj = node->GetObjectRef(); + if (!pObj) return NULL; + while (pObj->SuperClassID() == GEN_DERIVOB_CLASS_ID) + { + IDerivedObject* pDerObj = (IDerivedObject *)(pObj); + int Idx = 0; + while (Idx < pDerObj->NumModifiers()) + { + // Get the modifier. + Modifier* mod = pDerObj->GetModifier(Idx); + if (mod->ClassID() == SKIN_CLASSID) + { + // is this the correct Physique Modifier based on index? + return mod; + } + Idx++; + } + pObj = pDerObj->GetObjRef(); + } + + IDerivedObject *dobj = CreateDerivedObject(node->GetObjectRef()); + + //create a skin modifier and add it + Modifier *skinMod = (Modifier*) CreateInstance(OSM_CLASS_ID, SKIN_CLASSID); + dobj->SetAFlag(A_LOCK_TARGET); + dobj->AddModifier(skinMod); + dobj->ClearAFlag(A_LOCK_TARGET); + node->SetObjectRef(dobj); + return skinMod; +} + +// Set Position and Rotation on a standard controller will need to handle bipeds +// Always in World Transform coordinates +void PositionAndRotateNode(INode *n, Point3 p, Quat& q, TimeValue t) +{ + if (Control *c = n->GetTMController()) { + + // Bipeds are special. And will crash if you dont treat them with care + if ( (c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID) + ||(c->ClassID() == BIPBODY_CONTROL_CLASS_ID) + ||(c->ClassID() == FOOTPRINT_CLASS_ID)) + { + // Get the Biped Export Interface from the controller + //IBipedExport *BipIface = (IBipedExport *) c->GetInterface(I_BIPINTERFACE); + IOurBipExport *BipIface = (IOurBipExport *) c->GetInterface(I_OURINTERFACE); + BipIface->SetBipedRotation(q, t, n, 0/*???*/); + BipIface->SetBipedPosition(p, t, n); + } + else + { + if (Control *rotCtrl = c->GetRotationController()) + rotCtrl->SetValue(t, &q, 1, CTRL_ABSOLUTE); + if (Control *posCtrl = c->GetPositionController()) + posCtrl->SetValue(t, &p, 1, CTRL_ABSOLUTE); + } + } +} + +// Search NiNode collection for a specific name +NiNodeRef FindNodeByName( const vector<NiNodeRef>& blocks, const string& name ) +{ + for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr) + { + const NiNodeRef& block = (*itr); + if (name == block->GetName()) + return block; + } + return NiNodeRef(); +} + +// Search NiNode collection names that match a wildcard +vector<NiNodeRef> SelectNodesByName( const vector<NiNodeRef>& blocks, LPCTSTR match) +{ + vector<NiNodeRef> nodes; + for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr) + { + const NiNodeRef& block = (*itr); + if (wildcmpi(match, block->GetName().c_str())) + nodes.insert(nodes.end(), block); + } + return nodes; +} + +// Count number of NiNodes that match a wildcard +int CountNodesByName( const vector<NiNodeRef>& blocks, LPCTSTR match ) +{ + int count = 0; + for (vector<NiNodeRef>::const_iterator itr = blocks.begin(), end = blocks.end(); itr != end; ++itr) { + const NiNodeRef& block = (*itr); + if (wildcmpi(match, block->GetName().c_str())) + ++count; + } + return count; +} + +// Get a vector of names from an NiNode vector +vector<string> GetNamesOfNodes( const vector<Niflib::NiNodeRef>& nodes ) +{ + vector<string> slist; + for (vector<NiNodeRef>::const_iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr) { + const NiNodeRef& block = (*itr); + slist.push_back(block->GetName()); + } + return slist; +} + +// Recursively search through directories applying a filter on what to return +template <typename FileMatch> +void BuildFileNameMap(NameValueCollection & collection, const TCHAR *root, const TCHAR *path, FileMatch pred) +{ + TCHAR buffer[MAX_PATH], buffer2[MAX_PATH], search[MAX_PATH]; + WIN32_FIND_DATA FindFileData; + HANDLE hFind; + ZeroMemory(&FindFileData, sizeof(FindFileData)); + if (path == NULL || path[0] == 0) + return; + PathCanonicalize(search, path); + PathAddBackslash(search); + _tcscat(search, "*"); + + hFind = FindFirstFile(search, &FindFileData); + if (hFind != INVALID_HANDLE_VALUE) { + stringlist list; + for (BOOL ok = TRUE ; ok ; ok = FindNextFile(hFind, &FindFileData)) { + if (FindFileData.cFileName[0] == '.' || (FindFileData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN|FILE_ATTRIBUTE_SYSTEM))) + continue; + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + PathCombine(buffer, path, FindFileData.cFileName); + PathAddBackslash(buffer); + list.push_back(buffer); + } else { + if (pred(FindFileData.cFileName)) { + if (collection.find(FindFileData.cFileName) == collection.end()) { + PathCombine(buffer, path, FindFileData.cFileName); + GetLongPathName(buffer, buffer, MAX_PATH); + PathRemoveExtension(FindFileData.cFileName); + PathRelativePathTo(buffer2, root, FILE_ATTRIBUTE_DIRECTORY, buffer, FILE_ATTRIBUTE_NORMAL); + TCHAR *p = buffer2; while (*p == '\\') ++p; + collection.insert(KeyValuePair(FindFileData.cFileName, p)); + } + } + } + } + FindClose(hFind); + for (stringlist::iterator itr = list.begin(), end = list.end(); itr != end; ++itr) { + BuildFileNameMap(collection, root, (*itr).c_str(), pred); + } + } +} +// Implementation for BuildFileNameMap which will search for a specific set of extensions +struct ExtensionMatch : public std::unary_function<LPCTSTR, bool> +{ + stringlist extns; + ExtensionMatch(string extnlist) { + extns = TokenizeString(extnlist.c_str(), ";"); + } + ExtensionMatch(const stringlist& extnlist) : extns(extnlist) { + } + bool operator()(LPCTSTR name) const { + LPCSTR ext = PathFindExtension(name); + for (stringlist::const_iterator itr = extns.begin(), end = extns.end(); itr != end; ++itr) { + if (0 == _tcsicmp(ext, (*itr).c_str())) + return true; + } + return false; + } +}; + +// Run through the search paths and add them to the image collection +void FindImages(NameValueCollection& images, const string& rootPath, const stringlist& searchpaths, const stringlist& extensions) +{ + ExtensionMatch ddsMatch(extensions); + for (stringlist::const_iterator itr = searchpaths.begin(), end = searchpaths.end(); itr != end; ++itr) { + if (PathIsRelative((*itr).c_str())) + { + TCHAR texPath[MAX_PATH]; + PathCombine(texPath, rootPath.c_str(), (*itr).c_str()); + PathAddBackslash(texPath); + BuildFileNameMap(images, rootPath.c_str(), texPath, ddsMatch); + } + else + { + BuildFileNameMap(images, rootPath.c_str(), (*itr).c_str(), ddsMatch); + } + } +} diff --git a/MaxNifImport/niutils.h b/MaxNifImport/niutils.h new file mode 100644 index 0000000000000000000000000000000000000000..2408100e7f5b6af7d06891496592076282060c00 --- /dev/null +++ b/MaxNifImport/niutils.h @@ -0,0 +1,221 @@ +/********************************************************************** +*< +FILE: NIUtils.h + +DESCRIPTION: NifImporter Utilities + +CREATED BY: tazpn (Theo) + +HISTORY: + +INFO: See Implementation for minimalist comments + +*> Copyright (c) 2006, All Rights Reserved. +**********************************************************************/ +#ifndef _NIUTILS_H_ +#define _NIUTILS_H_ + +#ifndef _WINDOWS_ +# include <windows.h> +#endif +#include <tchar.h> +#include <string> +#include <map> +#include <vector> +#include <list> +#include <map> + +// Max Headers +#include <Max.h> +#include <strclass.h> +#include <color.h> + +// Niflib Headers +#include <obj\NiNode.h> +#include <nif_math.h> + +#ifndef _countof +#define _countof(x) (sizeof(x)/sizeof((x)[0])) +#endif + +extern int wildcmp(const TCHAR *wild, const TCHAR *string); +extern int wildcmpi(const TCHAR *wild, const TCHAR *string); + +// Trim whitespace before and after a string +inline TCHAR *Trim(TCHAR*&p) { + while(_istspace(*p)) *p++ = 0; + TCHAR *e = p + _tcslen(p) - 1; + while (e > p && _istspace(*e)) *e-- = 0; + return p; +} + +// Case insensitive string equivalence test for collections +struct ltstr +{ + bool operator()(const TCHAR* s1, const TCHAR* s2) const + { return _tcsicmp(s1, s2) < 0; } + + bool operator()(const std::string& s1, const std::string& s2) const + { return s1.compare(s2) < 0; } + + bool operator()(const std::string& s1, const TCHAR * s2) const + { return s1.compare(s2) < 0; } + + bool operator()(const TCHAR * s1, const std::string& s2) const + { return s2.compare(s1) >= 0; } +}; + +// Case insensitive string equivalence but numbers are sorted together +struct NumericStringEquivalence +{ + bool operator()(const TCHAR* s1, const TCHAR* s2) const + { return numstrcmp(s1, s2) < 0; } + + bool operator()(const std::string& s1, const TCHAR* s2) const + { return numstrcmp(s1.c_str(), s2) < 0; } + + bool operator()(const TCHAR* s1, const std::string& s2) const + { return numstrcmp(s1, s2.c_str()) < 0; } + + bool operator()(const std::string& s1, const std::string& s2) const + { return numstrcmp(s1.c_str(), s2.c_str()) < 0; } + + static int numstrcmp(const TCHAR *str1, const TCHAR *str2) + { + TCHAR *p1, *p2; + int c1, c2, lcmp; + for(;;) + { + c1 = tolower(*str1), c2 = tolower(*str2); + if ( c1 == 0 || c2 == 0 ) + break; + else if (isdigit(c1) && isdigit(c2)) + { + lcmp = strtol(str1, &p1, 10) - strtol(str2, &p2, 10); + if ( lcmp == 0 ) + lcmp = (p2 - str2) - (p1 - str1); + if ( lcmp != 0 ) + return (lcmp > 0 ? 1 : -1); + str1 = p1, str2 = p2; + } + else + { + lcmp = (c1 - c2); + if (lcmp != 0) + return (lcmp > 0 ? 1 : -1); + ++str1, ++str2; + } + } + lcmp = (c1 - c2); + return ( lcmp < 0 ) ? -1 : (lcmp > 0 ? 1 : 0); + } +}; + +// Generic IniFile reading routine +template<typename T> +inline T GetIniValue(LPCTSTR Section, LPCTSTR Setting, T Default, LPCTSTR iniFileName){ + T v; + TCHAR buffer[1024]; + stringstream sstr; + sstr << Default; + buffer[0] = 0; + if (0 < GetPrivateProfileString(Section, Setting, sstr.str().c_str(), buffer, sizeof(buffer), iniFileName)){ + stringstream sstr(buffer); + sstr >> v; + return v; + } + return Default; +} + +// Specific override for int values +template<> +inline int GetIniValue<int>(LPCTSTR Section, LPCTSTR Setting, int Default, LPCTSTR iniFileName){ + return GetPrivateProfileInt(Section, Setting, Default, iniFileName); +} + +// Specific override for string values +template<> +inline std::string GetIniValue<std::string>(LPCTSTR Section, LPCTSTR Setting, std::string Default, LPCTSTR iniFileName){ + TCHAR buffer[1024]; + buffer[0] = 0; + if (0 < GetPrivateProfileString(Section, Setting, Default.c_str(), buffer, sizeof(buffer), iniFileName)){ + return std::string(buffer); + } + return Default; +} + +// Specific override for TSTR values +template<> +inline TSTR GetIniValue<TSTR>(LPCTSTR Section, LPCTSTR Setting, TSTR Default, LPCTSTR iniFileName){ + TCHAR buffer[1024]; + buffer[0] = 0; + if (0 < GetPrivateProfileString(Section, Setting, Default.data(), buffer, sizeof(buffer), iniFileName)){ + return TSTR(buffer); + } + return Default; +} + +// Generic IniFile reading routine +template<typename T> +inline void SetIniValue(LPCTSTR Section, LPCTSTR Setting, T value, LPCTSTR iniFileName){ + stringstream sstr; + sstr << value; + WritePrivateProfileString(Section, Setting, sstr.str().c_str(), iniFileName); +} + +// Specific override for string values +template<> +inline void SetIniValue<std::string>(LPCTSTR Section, LPCTSTR Setting, std::string value, LPCTSTR iniFileName){ + WritePrivateProfileString(Section, Setting, value.c_str(), iniFileName); +} + +// Specific override for TSTR values +template<> +inline void SetIniValue<TSTR>(LPCTSTR Section, LPCTSTR Setting, TSTR value, LPCTSTR iniFileName){ + WritePrivateProfileString(Section, Setting, value.data(), iniFileName); +} + +// Common collections that I use +typedef std::map<std::string, std::string, ltstr> NameValueCollection; +typedef std::pair<std::string, std::string> KeyValuePair; +typedef std::list<std::string> stringlist; + +extern TSTR FormatText(const TCHAR* format,...); +extern std::string FormatString(const TCHAR* format,...); + +extern stringlist TokenizeString(LPCTSTR str, LPCTSTR delims); +extern string GetIndirectValue(LPCSTR path); +extern NameValueCollection ReadIniSection(LPCTSTR Section, LPCTSTR iniFileName ); +extern string ExpandQualifiers(const string& src, const NameValueCollection& map); +extern string ExpandEnvironment(const string& src); + +extern void FindImages(NameValueCollection& images, const string& rootPath, const stringlist& searchpaths, const stringlist& extensions); + +extern void RenameNode(Interface *gi, LPCTSTR SrcName, LPCTSTR DstName); +extern TriObject* GetTriObject(Object *o); +extern Modifier *GetSkin(INode *node); +extern void PositionAndRotateNode(INode *n, Point3 p, Quat& q, TimeValue t = 0); + +extern Niflib::NiNodeRef FindNodeByName( const vector<Niflib::NiNodeRef>& blocks, const string& name ); +extern std::vector<Niflib::NiNodeRef> SelectNodesByName( const vector<Niflib::NiNodeRef>& blocks, LPCTSTR match); +extern int CountNodesByName( const vector<Niflib::NiNodeRef>& blocks, LPCTSTR match ); +extern std::vector<std::string> GetNamesOfNodes( const vector<Niflib::NiNodeRef>& blocks ); + +// Simple conversion helpers +static inline float TODEG(float x) { return x * 180.0f / PI; } +static inline float TORAD(float x) { return x * PI / 180.0f; } + +static inline Color TOCOLOR(const Niflib::Color3& c3) { + return Color(c3.r, c3.g, c3.b); +} + +static inline Matrix3 TOMATRIX3(const Niflib::Matrix44 &tm, bool invert = true){ + Niflib::Vector3 pos; Niflib::Matrix33 rot; float scale; + tm.Decompose(pos, rot, scale); + Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3()); + if (invert) m.Invert(); + m.SetTrans(Point3(pos.x, pos.y, pos.z)); + return m; +} + +#endif // _NIUTILS_H_ \ No newline at end of file diff --git a/MaxNifImport/objectParams.h b/MaxNifImport/objectParams.h new file mode 100644 index 0000000000000000000000000000000000000000..d0367e6f0e28ba5b42ba0fb9ba8db7220d3ef306 --- /dev/null +++ b/MaxNifImport/objectParams.h @@ -0,0 +1,336 @@ +/*===========================================================================*\ + | + | FILE: objectParams.h + | Skeleton project and code for a Utility + | 3D Studio MAX R3.0 + | + | AUTH: Cleve Ard + | Render Group + | Copyright(c) Discreet 2000 + | + | HIST: Started 9-8-00 + | +\*===========================================================================*/ + +#ifndef _OBJECTPARAMS_H_ +#define _OBJECTPARAMS_H_ + +#include "maxscrpt/maxscrpt.h" +#include "maxscrpt/Numbers.h" +#include "maxscrpt/Name.h" +#include "maxscrpt/ColorVal.h" +#include "maxscrpt/MAXObj.h" +#include "assert1.h" + +template <class T> +class ConvertMAXScriptToC; +inline Value* make_maxscript_value(bool v); +inline Value* make_maxscript_value(int v); +inline Value* make_maxscript_value(float v); +inline Value* make_maxscript_value(COLORREF rgb); +inline Value* make_maxscript_value(const Color& rgb); +inline Value* make_maxscript_value(LPCTSTR str); +inline Value* make_maxscript_value(ReferenceTarget* rtarg); + +// Not all of the paramters for materials and shaders +// are published in the interfaces. ObjectParams class +// is used to access the properties of objects the same +// way the scriptor does, so you can get to properties. + +// Set the array parameter named name to the value at time t. +template<class T> +bool setMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T value, int tabIndex) +{ + bool rval = false; // Return false if fails + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + two_value_locals(prop, result); // Keep two local variables + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name of the parameter and then retrieve + // the array holding the value we want to set. + vl.prop = Name::intern(name); + vl.result = MAXWrapper::get_property(obj, vl.prop, NULL); + + // Make sure it is the right type. + if (vl.result != NULL && vl.result->tag == class_tag(MAXPB2ArrayParam)) { + // OK. Now we make sure the tabIndex is within the array bounds. + MAXPB2ArrayParam* array = static_cast<MAXPB2ArrayParam*>(vl.result); + if (array->pblock != NULL && array->pdef != NULL + && tabIndex >= 0 && tabIndex < array->pblock->Count(array->pdef->ID)) { + // Set the value in the array. + array->pblock->SetValue(array->pdef->ID, 0, value, tabIndex); + rval = true; // Succeeded + } + } + } catch ( ... ) { + // Failed. + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + +// Set the parameter. Cannot be an array entry +template<class T> +bool setMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value) +{ + bool rval = false; // return false if fails + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + two_value_locals(prop, val); // Keep two local variables + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name and value to set + vl.prop = Name::intern(name); + vl.val = make_maxscript_value(value); + + // Set the value. + Value* val = MAXWrapper::set_property(obj, vl.prop, vl.val); + if (val != NULL) + rval = true; // Succeeded + } catch ( ... ) { + // Failed. + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + +// Get the parameter from an array +template<class T> +bool getMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value, int tabIndex) +{ + bool rval = false; // Return false if fails + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + two_value_locals(prop, result); // Keep two local variables + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name and the array holding the roperty. + vl.prop = Name::intern(name); + vl.result = MAXWrapper::get_property(obj, vl.prop, NULL); + + // Make sure it is they right type. + if (vl.result != NULL && is_tab_param(vl.result)) { + // OK. Now we make sure the tabIndex is within the array bounds. + MAXPB2ArrayParam* array = static_cast<MAXPB2ArrayParam*>(vl.result); + if (array->pblock != NULL && array->pdef != NULL + && tabIndex >= 0 && tabIndex < array->pblock->Count(array->pdef->ID)) { + // Good. Get the value + array->pblock->GetValue(array->pdef->ID, 0, value, Interval(0,0), tabIndex); + rval = true; // Succeeded + } + } + } catch ( ... ) { + // Failed. + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + +// Get the parameter +template<class T> +bool getMAXScriptValue(ReferenceTarget* obj, LPTSTR name, TimeValue t, T& value) +{ + bool rval = false; // Return false if fails + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + two_value_locals(prop, result); // Keep two local varaibles + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name and the parameter value + vl.prop = Name::intern(name); + vl.result = MAXWrapper::get_property(obj, vl.prop, NULL); + + // Make sure it is valid. + if (vl.result != NULL && vl.result != &undefined && vl.result != &unsupplied) { + // Convert to C++ type. + value = ConvertMAXScriptToC<T>::cvt(vl.result); + rval = true; // Succeeded + } + } catch ( ... ) { + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + +// Get the parameter controller +Control* getMAXScriptController(ReferenceTarget* obj, LPTSTR name, ParamDimension*& dim) +{ + Control* rval = NULL; + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + one_value_local(prop); // Keep one local varaibles + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name and the parameter value + vl.prop = Name::intern(name); + rval = MAXWrapper::get_max_prop_controller(obj, vl.prop, &dim); + } catch ( ... ) { + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + +// Set the parameter controller +bool setMAXScriptController(ReferenceTarget* obj, LPTSTR name, Control* control, ParamDimension* dim) +{ + bool rval = false; + assert(obj != NULL); + init_thread_locals(); + push_alloc_frame(); + two_value_locals(prop, maxControl); // Keep two local varaibles + save_current_frames(); + trace_back_active = FALSE; + + try { + // Get the name and the parameter value + vl.prop = Name::intern(name); + vl.maxControl = MAXControl::intern(control, dim); + rval = MAXWrapper::set_max_prop_controller(obj, vl.prop, + static_cast<MAXControl*>(vl.maxControl)) != 0; + } catch ( ... ) { + clear_error_source_data(); + restore_current_frames(); + MAXScript_signals = 0; + if (progress_bar_up) + MAXScript_interface->ProgressEnd(), progress_bar_up = FALSE; + } + pop_value_locals(); + pop_alloc_frame(); + return rval; +} + + +// These helpers are used to convert C++ values to +// their MAXScript value. Maxscript uses different +// methods to handle the conversion, and these helpers +// allow the conversion to be templated. You may need +// to add more converter, if you need to access other +// parameter types. +inline Value* make_maxscript_value(bool v) +{ + return_protected(v ? &true_value : &false_value); +} + +inline Value* make_maxscript_value(int v) +{ + return_protected(Integer::intern(v)); +} + +inline Value* make_maxscript_value(float v) +{ + return_protected(Float::intern(v)); +} + +inline Value* make_maxscript_value(COLORREF rgb) +{ + return new ColorValue(rgb); +} + +inline Value* make_maxscript_value(const Color& rgb) +{ + return new ColorValue(rgb); +} + +inline Value* make_maxscript_value(LPCTSTR str) +{ + return Name::intern(const_cast<LPTSTR>(str)); +} + +inline Value* make_maxscript_value(ReferenceTarget* rtarg) +{ + return MAXClass::make_wrapper_for(rtarg); +} + +// These helpers convert MAXScript values to C++ values. +// MAXScript uses different methods for this conversion, +// and these helpers template the conversion. You will +// need to add more converters if you need more types. +template <class T> +class ConvertMAXScriptToC { +public: + static T cvt(Value* val); +}; + +inline bool ConvertMAXScriptToC<bool>::cvt(Value* val) +{ + return val->to_bool() != 0; +} + +inline int ConvertMAXScriptToC<int>::cvt(Value* val) +{ + return val->to_int(); +} + +inline float ConvertMAXScriptToC<float>::cvt(Value* val) +{ + return val->to_float(); +} + +inline Color ConvertMAXScriptToC<Color>::cvt(Value* val) +{ + return val->to_point3(); +} + +inline LPTSTR ConvertMAXScriptToC<LPTSTR>::cvt(Value* val) +{ + return val->to_string(); +} + +inline Texmap* ConvertMAXScriptToC<Texmap*>::cvt(Value* val) +{ + return val->to_texmap(); +} + +inline ReferenceTarget* ConvertMAXScriptToC<ReferenceTarget*>::cvt(Value* val) +{ + return val->to_reftarg(); +} + + +#endif diff --git a/MaxNifImport/resource.h b/MaxNifImport/resource.h new file mode 100644 index 0000000000000000000000000000000000000000..01805df3283da03790417e21430b878414cedeee --- /dev/null +++ b/MaxNifImport/resource.h @@ -0,0 +1,28 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by MaxNifImport.rc +// +#define IDS_LIBDESCRIPTION 1 +#define IDS_CATEGORY 2 +#define IDS_CLASS_NAME 3 +#define IDS_PARAMS 4 +#define IDS_SPIN 5 +#define IDD_PANEL 101 +#define IDC_CLOSEBUTTON 1000 +#define IDC_DOSTUFF 1000 +#define IDC_EDITHEIGHT 1001 +#define IDC_BUTTON1 1002 +#define IDC_COLOR 1456 +#define IDC_EDIT 1490 +#define IDC_SPIN 1496 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1003 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/MaxNifImport/stdafx.cpp b/MaxNifImport/stdafx.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1577c4e3bcb4044fc1878341fd4c5dc22157400e --- /dev/null +++ b/MaxNifImport/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" \ No newline at end of file diff --git a/MaxNifImport/stdafx.h b/MaxNifImport/stdafx.h new file mode 100644 index 0000000000000000000000000000000000000000..9f724ac266426cd0f9fe74c0a6f68ca963e1b76e --- /dev/null +++ b/MaxNifImport/stdafx.h @@ -0,0 +1,28 @@ +#include <stdio.h> +#include <tchar.h> +#include <iomanip> +#include <cmath> +#include <iostream> +#include <fstream> +#include <string> +#include <map> +#include <exception> +#include <stdexcept> +#include <algorithm> +#include <functional> +#include <numeric> +#include <vector> +#include <list> +#include <map> + +// _WIN32 will detect windows on most compilers +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <commctrl.h> +#include <shlwapi.h> + +#include "Max.h" +#include "resource.h" +#include "istdplug.h" +#include "iparamb2.h" +#include "iparamm2.h"