aboutsummaryrefslogtreecommitdiff
path: root/NvBlast/tools/common
diff options
context:
space:
mode:
authorBryan Galdrikian <[email protected]>2017-02-21 12:07:59 -0800
committerBryan Galdrikian <[email protected]>2017-02-21 12:07:59 -0800
commit446ce137c6823ba9eff273bdafdaf266287c7c98 (patch)
treed20aab3e2ed08d7b3ca71c2f40db6a93ea00c459 /NvBlast/tools/common
downloadblast-1.0.0-beta.tar.xz
blast-1.0.0-beta.zip
first commitv1.0.0-beta
Diffstat (limited to 'NvBlast/tools/common')
-rw-r--r--NvBlast/tools/common/BlastDataExporter.cpp106
-rw-r--r--NvBlast/tools/common/BlastDataExporter.h78
-rw-r--r--NvBlast/tools/common/FbxFileReader.cpp170
-rw-r--r--NvBlast/tools/common/FbxFileReader.h23
-rw-r--r--NvBlast/tools/common/FbxFileWriter.cpp645
-rw-r--r--NvBlast/tools/common/FbxFileWriter.h49
-rw-r--r--NvBlast/tools/common/FbxUtils.cpp30
-rw-r--r--NvBlast/tools/common/FbxUtils.h20
-rw-r--r--NvBlast/tools/common/IMeshFileReader.h21
-rw-r--r--NvBlast/tools/common/IMeshFileWriter.h41
-rw-r--r--NvBlast/tools/common/Log.cpp59
-rw-r--r--NvBlast/tools/common/Log.h122
-rw-r--r--NvBlast/tools/common/ObjFileReader.cpp63
-rw-r--r--NvBlast/tools/common/ObjFileReader.h16
-rw-r--r--NvBlast/tools/common/ObjFileWriter.cpp142
-rw-r--r--NvBlast/tools/common/ObjFileWriter.h26
-rw-r--r--NvBlast/tools/common/Utils.cpp166
-rw-r--r--NvBlast/tools/common/Utils.h113
18 files changed, 1890 insertions, 0 deletions
diff --git a/NvBlast/tools/common/BlastDataExporter.cpp b/NvBlast/tools/common/BlastDataExporter.cpp
new file mode 100644
index 0000000..622901a
--- /dev/null
+++ b/NvBlast/tools/common/BlastDataExporter.cpp
@@ -0,0 +1,106 @@
+#include <BlastDataExporter.h>
+#include "NvBlastExtPxManager.h"
+#include <NvBlastExtAuthoringCollisionBuilder.h>
+#include <Log.h>
+#include "PsFileBuffer.h"
+#include "NvBlastExtPxAsset.h"
+#include "NvBlast.h"
+#include <NvBlastTkAsset.h>
+
+using namespace Nv::Blast;
+
+
+ExtPxAsset* BlastDataExporter::createExtBlastAsset(std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs,
+ std::vector<ExtPxAssetDesc::ChunkDesc>& physicsChunks)
+{
+ ExtPxAssetDesc descriptor;
+ descriptor.bondCount = static_cast<uint32_t>(bondDescs.size());
+ descriptor.bondDescs = bondDescs.data();
+ descriptor.chunkCount = static_cast<uint32_t>(chunkDescs.size());
+ descriptor.chunkDescs = chunkDescs.data();
+ descriptor.bondFlags = nullptr;
+ descriptor.pxChunks = physicsChunks.data();
+ ExtPxAsset* asset = ExtPxAsset::create(descriptor, *mFramework);
+ return asset;
+}
+
+
+
+NvBlastAsset* BlastDataExporter::createLlBlastAsset(std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs)
+{
+
+ NvBlastAssetDesc assetDesc;
+ assetDesc.bondCount = static_cast<uint32_t>(bondDescs.size());
+ assetDesc.bondDescs = &bondDescs[0];
+
+ assetDesc.chunkCount = static_cast<uint32_t>(chunkDescs.size());
+ assetDesc.chunkDescs = &chunkDescs[0];
+
+
+ std::vector<uint8_t> scratch(static_cast<unsigned int>(NvBlastGetRequiredScratchForCreateAsset(&assetDesc, m_log)));
+ void* mem = _aligned_malloc(NvBlastGetAssetMemorySize(&assetDesc, m_log), 16);
+ NvBlastAsset* asset = NvBlastCreateAsset(mem, &assetDesc, &scratch[0], m_log);
+ return asset;
+}
+
+TkAsset* BlastDataExporter::createTkBlastAsset(const std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs)
+{
+ TkAssetDesc desc;
+ desc.bondCount = static_cast<uint32_t>(bondDescs.size());
+ desc.bondDescs = bondDescs.data();
+ desc.chunkCount = static_cast<uint32_t>(chunkDescs.size());
+ desc.chunkDescs = chunkDescs.data();
+ desc.bondFlags = nullptr;
+ TkAsset* asset = mFramework->createAsset(desc);
+ return asset;
+};
+
+
+bool BlastDataExporter::saveBlastLLAsset(const std::string& outputFilePath, const NvBlastAsset* asset)
+{
+ uint32_t assetSize = NvBlastAssetGetSize(asset, m_log);
+
+ physx::PsFileBuffer fileBuf(outputFilePath.c_str(), physx::PxFileBuf::OPEN_WRITE_ONLY);
+ if (!fileBuf.isOpen())
+ {
+ NVBLAST_LOG_ERROR(m_log, "Can't open output buffer. \n");
+ return false;
+ }
+ fileBuf.write(asset, sizeof(char) * assetSize);
+ fileBuf.close();
+ return true;
+}
+
+bool BlastDataExporter::saveBlastTkAsset(const std::string& outputFilePath, const TkAsset* asset)
+{
+ physx::PsFileBuffer fileBuf(outputFilePath.c_str(), physx::PxFileBuf::OPEN_WRITE_ONLY);
+ if (!fileBuf.isOpen())
+ {
+ NVBLAST_LOG_ERROR(m_log, "Can't open output buffer. \n");
+ return false;
+ }
+ if (!asset->serialize(fileBuf))
+ {
+ NVBLAST_LOG_ERROR(m_log, "Serialization failed. \n");
+ return false;
+ }
+ fileBuf.close();
+ return true;
+}
+
+bool BlastDataExporter::saveBlastExtAsset(const std::string& outputFilePath, const ExtPxAsset* asset)
+{
+ physx::PsFileBuffer fileBuf(outputFilePath.c_str(), physx::PxFileBuf::OPEN_WRITE_ONLY);
+ if (!fileBuf.isOpen())
+ {
+ NVBLAST_LOG_ERROR(m_log, "Can't open output buffer. \n");
+ return false;
+ }
+ if (!asset->serialize(fileBuf, *mCooking))
+ {
+ NVBLAST_LOG_ERROR(m_log, "ExtPhysicsAsset serialization failed.\n");
+ return false;
+ }
+ fileBuf.close();
+ return true;
+} \ No newline at end of file
diff --git a/NvBlast/tools/common/BlastDataExporter.h b/NvBlast/tools/common/BlastDataExporter.h
new file mode 100644
index 0000000..3e44a7c
--- /dev/null
+++ b/NvBlast/tools/common/BlastDataExporter.h
@@ -0,0 +1,78 @@
+#ifndef BLAST_DATA_EXPORTER
+#define BLAST_DATA_EXPORTER
+
+
+#include <NvBlastIndexFns.h>
+#include <NvBlastExtAuthoringTypes.h>
+#include <NvBlastExtPxAsset.h>
+#include <vector>
+#include <string>
+
+using namespace Nv::Blast;
+
+namespace physx
+{
+ class PxCooking;
+}
+
+
+struct NvBlastBondDesc;
+struct NvBlastChunkDesc;
+
+struct NvBlastAsset;
+namespace Nv
+{
+ namespace Blast
+ {
+ class TkAsset;
+ class ExtPxAsset;
+ }
+}
+/**
+ Tool for Blast asset creation and exporting
+*/
+class BlastDataExporter
+{
+public:
+ BlastDataExporter(TkFramework* framework, physx::PxCooking* cooking, NvBlastLog log) : mFramework(framework), mCooking(cooking), m_log(log) {};
+
+ /**
+ Creates ExtPxAsset
+ */
+ ExtPxAsset* createExtBlastAsset(std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs,
+ std::vector<ExtPxAssetDesc::ChunkDesc>& physicsChunks);
+ /**
+ Creates Low Level Blast asset
+ */
+ NvBlastAsset* createLlBlastAsset(std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs);
+
+ /**
+ Creates Blast Toolkit Asset asset
+ */
+ TkAsset* createTkBlastAsset(const std::vector<NvBlastBondDesc>& bondDescs, const std::vector<NvBlastChunkDesc>& chunkDescs);
+
+ /*
+ Saves Blast LL asset to given path
+ */
+ bool saveBlastLLAsset(const std::string& outputFilePath, const NvBlastAsset* asset);
+
+ /*
+ Saves Blast Tk asset to given path
+ */
+ bool saveBlastTkAsset(const std::string& outputFilePath, const TkAsset* asset);
+
+ /*
+ Saves Blast BPXA asset to given path
+ */
+ bool saveBlastExtAsset(const std::string& outputFilePath, const ExtPxAsset* asset);
+
+private:
+ TkFramework* mFramework;
+ physx::PxCooking* mCooking;
+ NvBlastLog m_log;
+};
+
+
+
+
+#endif \ No newline at end of file
diff --git a/NvBlast/tools/common/FbxFileReader.cpp b/NvBlast/tools/common/FbxFileReader.cpp
new file mode 100644
index 0000000..6b72223
--- /dev/null
+++ b/NvBlast/tools/common/FbxFileReader.cpp
@@ -0,0 +1,170 @@
+#include "FbxFileReader.h"
+#include "fileio/fbxiosettings.h"
+#include "fileio/fbxiosettingspath.h"
+#include "core/base/fbxstringlist.h"
+#include <iostream>
+#include "scene/geometry/fbxmesh.h"
+
+#include "PxVec3.h"
+#include "PxVec2.h"
+
+using physx::PxVec3;
+using physx::PxVec2;
+using Nv::Blast::Mesh;
+
+
+FbxFileReader::FbxFileReader()
+{
+ setConvertToUE4(false);
+}
+
+FbxAMatrix FbxFileReader::getTransformForNode(FbxNode* node)
+{
+ return node->EvaluateGlobalTransform();
+}
+
+std::shared_ptr<Nv::Blast::Mesh> FbxFileReader::loadFromFile(std::string filename)
+{
+ // Wrap in a shared ptr so that when it deallocates we get an auto destroy and all of the other assets created don't leak.
+ std::shared_ptr<FbxManager> sdkManager = std::shared_ptr<FbxManager>(FbxManager::Create(), [=](FbxManager* manager)
+ {
+ std::cout << "Deleting FbxManager" << std::endl;
+ manager->Destroy();
+ });
+
+ FbxIOSettings* ios = FbxIOSettings::Create(sdkManager.get(), IOSROOT);
+ // Set some properties on the io settings
+
+ sdkManager->SetIOSettings(ios);
+
+
+ FbxImporter* importer = FbxImporter::Create(sdkManager.get(), "");
+
+ bool importStatus = importer->Initialize(filename.c_str(), -1, sdkManager->GetIOSettings());
+
+ if (!importStatus)
+ {
+ std::cerr << "Call to FbxImporter::Initialize failed." << std::endl;
+ std::cerr << "Error returned: " << importer->GetStatus().GetErrorString() << std::endl;
+
+ return nullptr;
+ }
+
+ FbxScene* scene = FbxScene::Create(sdkManager.get(), "importScene");
+
+ importStatus = importer->Import(scene);
+
+ if (!importStatus)
+ {
+ std::cerr << "Call to FbxImporter::Import failed." << std::endl;
+ std::cerr << "Error returned: " << importer->GetStatus().GetErrorString() << std::endl;
+
+ return nullptr;
+ }
+
+ if (getConvertToUE4())
+ {
+ // Convert to UE4
+ FbxAxisSystem::EFrontVector FrontVector = (FbxAxisSystem::EFrontVector) - FbxAxisSystem::eParityOdd;
+ const FbxAxisSystem UnrealZUp(FbxAxisSystem::eZAxis, FrontVector, FbxAxisSystem::eRightHanded);
+
+ UnrealZUp.ConvertScene(scene);
+ }
+
+
+ // Recurse the fbx tree and find all meshes
+ std::vector<FbxNode*> meshNodes;
+ getFbxMeshes(scene->GetRootNode(), meshNodes);
+
+ std::cout << "Found " << meshNodes.size() << " meshes." << std::endl;
+
+ // Process just 0, because dumb. Fail out if more than 1?
+
+ FbxNode* meshNode = meshNodes[0];
+ FbxMesh* mesh = meshNode->GetMesh();
+
+ int polyCount = mesh->GetPolygonCount();
+
+
+ bool bAllTriangles = true;
+ // Verify that the mesh is triangulated.
+ for (int i = 0; i < polyCount; i++)
+ {
+ if (mesh->GetPolygonSize(i) != 3)
+ {
+ bAllTriangles = false;
+ }
+ }
+
+ if (!bAllTriangles)
+ {
+ std::cerr << "Mesh 0 has " << polyCount << " but not all polygons are triangles. Mesh must be triangulated." << std::endl;
+ return nullptr;
+ }
+
+ FbxStringList uvSetNames;
+
+ mesh->GetUVSetNames(uvSetNames);
+
+ const char * uvSetName = uvSetNames.GetStringAt(0);
+
+ std::vector<PxVec3> positions;
+ std::vector<PxVec3> normals;
+ std::vector<PxVec2> uv;
+ std::vector<uint32_t> indices;
+
+ int* polyVertices = mesh->GetPolygonVertices();
+
+ uint32_t vertIndex = 0;
+
+ FbxAMatrix trans = getTransformForNode(meshNode);
+
+ for (int i = 0; i < polyCount; i++)
+ {
+ for (int vi = 0; vi < 3; vi++)
+ {
+ int polyCPIdx = polyVertices[i*3+vi];
+
+ FbxVector4 vert = mesh->GetControlPointAt(polyCPIdx);
+ FbxVector4 normVec;
+ FbxVector2 uvVec;
+ bool bUnmapped;
+ mesh->GetPolygonVertexNormal(i, vi, normVec);
+ mesh->GetPolygonVertexUV(i, vi, uvSetName, uvVec, bUnmapped);
+
+ vert = trans.MultT(vert);
+ normVec = trans.MultT(normVec);
+
+ positions.push_back(PxVec3((float) vert[0], (float)vert[1], (float)vert[2]));
+ normals.push_back(PxVec3((float)normVec[0], (float)normVec[1], (float)normVec[2]));
+ uv.push_back(PxVec2((float)uvVec[0], (float)uvVec[1]));
+
+ indices.push_back(vertIndex++);
+ }
+
+ }
+
+ PxVec3* nr = (!normals.empty()) ? normals.data() : nullptr;
+ PxVec2* uvp = (!uv.empty()) ? uv.data() : nullptr;
+
+ return std::make_shared<Mesh>(positions.data(), nr, uvp, static_cast<uint32_t>(positions.size()), indices.data(), static_cast<uint32_t>(indices.size()));
+}
+
+void FbxFileReader::getFbxMeshes(FbxNode* node, std::vector<FbxNode*>& meshNodes)
+{
+ FbxMesh* mesh = node->GetMesh();
+
+ if (mesh != nullptr)
+ {
+ meshNodes.push_back(node);
+ }
+
+ int childCount = node->GetChildCount();
+
+ for (int i = 0; i < childCount; i++)
+ {
+ FbxNode * childNode = node->GetChild(i);
+
+ getFbxMeshes(childNode, meshNodes);
+ }
+}
diff --git a/NvBlast/tools/common/FbxFileReader.h b/NvBlast/tools/common/FbxFileReader.h
new file mode 100644
index 0000000..ca288ad
--- /dev/null
+++ b/NvBlast/tools/common/FbxFileReader.h
@@ -0,0 +1,23 @@
+#pragma once
+#include "IMeshFileReader.h"
+#include "fbxsdk.h"
+
+class FbxFileReader: public IMeshFileReader
+{
+public:
+ FbxFileReader();
+ ~FbxFileReader() = default;
+
+ /*
+ Load from the specified file path, returning a mesh or nullptr if failed
+ */
+ std::shared_ptr<Nv::Blast::Mesh> loadFromFile(std::string filename) override;
+
+private:
+
+ // Should we convert the scene to UE4 coordinate system on load?
+ bool bConvertToUE4;
+
+ FbxAMatrix getTransformForNode(FbxNode* node);
+ void getFbxMeshes(FbxNode* node, std::vector<FbxNode*>& meshNodes);
+}; \ No newline at end of file
diff --git a/NvBlast/tools/common/FbxFileWriter.cpp b/NvBlast/tools/common/FbxFileWriter.cpp
new file mode 100644
index 0000000..a71896e
--- /dev/null
+++ b/NvBlast/tools/common/FbxFileWriter.cpp
@@ -0,0 +1,645 @@
+#include "FbxFileWriter.h"
+#include "fbxsdk.h"
+#include "FbxUtils.h"
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+#include "NvBlastTypes.h"
+#include "NvBlastTkFramework.h"
+#include "NvBlast.h"
+#include "PxVec3.h"
+#include "NvBlastAssert.h"
+#include <unordered_set>
+#include <functional>
+
+
+FbxFileWriter::FbxFileWriter():
+ bOutputFBXAscii(false)
+{
+ // Wrap in a shared ptr so that when it deallocates we get an auto destroy and all of the other assets created don't leak.
+ sdkManager = std::shared_ptr<FbxManager>(FbxManager::Create(), [=](FbxManager* manager)
+ {
+ std::cout << "Deleting FbxManager" << std::endl;
+ manager->Destroy();
+ });
+
+ setConvertToUE4(false);
+}
+
+/*
+
+ Add the NvBlastAsset as a param
+ Walk the NvBlastChunk tree
+ -- Get the triangles for each chunk, however we do that
+ -- create a skin, clusters and bone node for each chunk, linked to their parent with the proper link mode
+
+*/
+bool FbxFileWriter::saveToFile(const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry, std::string assetName, std::string outputPath)
+{
+
+ FbxIOSettings* ios = FbxIOSettings::Create(sdkManager.get(), IOSROOT);
+ // Set some properties on the io settings
+
+// ios->SetBoolProp(EXP_ASCIIFBX, true);
+
+ sdkManager->SetIOSettings(ios);
+
+ sdkManager->GetIOSettings()->SetBoolProp(EXP_ASCIIFBX, bOutputFBXAscii);
+
+ FbxScene* scene = FbxScene::Create(sdkManager.get(), "Export Scene");
+
+ if (getConvertToUE4())
+ {
+ FbxAxisSystem::EFrontVector FrontVector = (FbxAxisSystem::EFrontVector) - FbxAxisSystem::eParityOdd;
+ const FbxAxisSystem UnrealZUp(FbxAxisSystem::eZAxis, FrontVector, FbxAxisSystem::eRightHanded);
+
+ scene->GetGlobalSettings().SetAxisSystem(UnrealZUp);
+ }
+
+ // Otherwise default to Maya defaults
+
+ FbxMesh* mesh = FbxMesh::Create(sdkManager.get(), "meshgeo");
+
+ FbxGeometryElementNormal* geNormal = mesh->CreateElementNormal();
+ geNormal->SetMappingMode(FbxGeometryElement::eByControlPoint);
+ geNormal->SetReferenceMode(FbxGeometryElement::eDirect);
+
+ FbxGeometryElementUV* geUV = mesh->CreateElementUV("diffuseElement");
+ geUV->SetMappingMode(FbxGeometryElement::eByPolygonVertex);
+ geUV->SetReferenceMode(FbxGeometryElement::eDirect);
+
+ // Get the triangles count for all of the mesh parts
+
+ size_t triangleCount = 0;
+ for (auto triangles : chunksGeometry)
+ {
+ triangleCount += triangles.size();
+ }
+
+ mesh->InitControlPoints((int)triangleCount * 3);
+
+ FbxNode* meshNode = FbxNode::Create(scene, "meshnode");
+ meshNode->SetNodeAttribute(mesh);
+ meshNode->SetShadingMode(FbxNode::eTextureShading);
+
+ FbxNode* lRootNode = scene->GetRootNode();
+ lRootNode->AddChild(meshNode);
+
+ FbxSkin* skin = FbxSkin::Create(sdkManager.get(), "Skin of the thing");
+ skin->SetGeometry(mesh);
+
+ mesh->AddDeformer(skin);
+
+ // Add a material otherwise UE4 freaks out on import
+
+ FbxGeometryElementMaterial* matElement = mesh->CreateElementMaterial();
+ matElement->SetMappingMode(FbxGeometryElement::eByPolygon);
+ matElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
+
+ FbxSurfacePhong* material = FbxSurfacePhong::Create(sdkManager.get(), "FirstExportMaterial");
+
+ material->Diffuse.Set(FbxDouble3(1.0, 1.0, 0));
+ material->DiffuseFactor.Set(1.0);
+
+ meshNode->AddMaterial(material);
+
+ FbxSurfacePhong* material2 = FbxSurfacePhong::Create(sdkManager.get(), "SecondExportMaterial");
+
+ material2->Diffuse.Set(FbxDouble3(1.0, 0.0, 1.0));
+ material2->DiffuseFactor.Set(1.0);
+
+ meshNode->AddMaterial(material2);
+
+ // Now walk the tree and create a skeleton with geometry at the same time
+ // Find a "root" chunk and walk the tree from there.
+ uint32_t chunkCount = NvBlastAssetGetChunkCount(asset, NvBlastTkFrameworkGet()->getLogFn());
+
+ auto chunks = NvBlastAssetGetChunks(asset, NvBlastTkFrameworkGet()->getLogFn());
+
+ currentDepth = 0;
+ uint32_t cpIdx = 0;
+ for (uint32_t i = 0; i < chunkCount; i++)
+ {
+ const NvBlastChunk* chunk = &chunks[i];
+
+ if (chunk->parentChunkIndex == UINT32_MAX)
+ {
+ uint32_t addedCps = createChunkRecursive(cpIdx, i, meshNode, lRootNode, skin, asset, chunksGeometry);
+
+ cpIdx += addedCps;
+ }
+ }
+
+ return finalizeFbxAndSave(scene, skin, outputPath, assetName);}
+
+/*
+ Recursive method that creates this chunk and all it's children.
+
+ This creates a FbxNode with an FbxCluster, and all of the geometry for this chunk.
+
+ Returns the number of added control points
+*/
+uint32_t FbxFileWriter::createChunkRecursive(uint32_t currentCpIdx, uint32_t chunkIndex, FbxNode *meshNode, FbxNode* parentNode, FbxSkin* skin, const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry)
+{
+ currentDepth++;
+
+// if (currentDepth >= 4)
+// {
+// return 0;
+// }
+
+ auto chunks = NvBlastAssetGetChunks(asset, NvBlastTkFrameworkGet()->getLogFn());
+ const NvBlastChunk* chunk = &chunks[chunkIndex];
+ auto triangles = chunksGeometry[chunkIndex];
+ physx::PxVec3 centroid = physx::PxVec3(chunk->centroid[0], chunk->centroid[1], chunk->centroid[2]);
+
+ std::ostringstream namestream;
+
+ //mesh->InitTextureUV(triangles.size() * 3);
+
+ std::ostringstream().swap(namestream); // Swap namestream with a default constructed ostringstream
+ namestream << "bone_" << chunkIndex;
+ std::string boneName = namestream.str();
+
+ FbxSkeleton* skelAttrib;
+ if (chunk->parentChunkIndex == UINT32_MAX)
+ {
+ skelAttrib = FbxSkeleton::Create(sdkManager.get(), "SkelRootAttrib");
+ skelAttrib->SetSkeletonType(FbxSkeleton::eRoot);
+
+ // Change the centroid to origin
+ centroid = physx::PxVec3(0.0f);
+ }
+ else
+ {
+ skelAttrib = FbxSkeleton::Create(sdkManager.get(), boneName.c_str());
+ skelAttrib->SetSkeletonType(FbxSkeleton::eLimbNode);
+ }
+
+ skelAttrib->Size.Set(1.0); // What's this for?
+
+
+ FbxNode* boneNode = FbxNode::Create(sdkManager.get(), boneName.c_str());
+ boneNode->SetNodeAttribute(skelAttrib);
+
+ auto mat = parentNode->EvaluateGlobalTransform().Inverse();
+
+ FbxVector4 vec(centroid.x, centroid.y, centroid.z, 0);
+ FbxVector4 c2 = mat.MultT(vec);
+
+ boneNode->LclTranslation.Set(c2);
+
+ parentNode->AddChild(boneNode);
+
+ std::ostringstream().swap(namestream); // Swap namestream with a default constructed ostringstream
+ namestream << "cluster_" << std::setw(5) << std::setfill('0') << chunkIndex;
+ std::string clusterName = namestream.str();
+
+ FbxCluster* cluster = FbxCluster::Create(sdkManager.get(), clusterName.c_str());
+ cluster->SetTransformMatrix(FbxAMatrix());
+ cluster->SetLink(boneNode);
+ cluster->SetLinkMode(FbxCluster::eTotalOne);
+
+ skin->AddCluster(cluster);
+
+ FbxMesh* mesh = static_cast<FbxMesh*>(meshNode->GetNodeAttribute());
+
+ FbxVector4* controlPoints = mesh->GetControlPoints();
+ auto geNormal = mesh->GetElementNormal();
+ auto geUV = mesh->GetElementUV("diffuseElement");
+ FbxGeometryElementMaterial* matElement = mesh->GetElementMaterial();
+
+ auto addVert = [&](Nv::Blast::Vertex vert, int controlPointIdx)
+ {
+ FbxVector4 vertex;
+ FbxVector4 normal;
+ FbxVector2 uv;
+
+ FbxUtils::VertexToFbx(vert, vertex, normal, uv);
+
+ controlPoints[controlPointIdx] = vertex;
+ geNormal->GetDirectArray().Add(normal);
+ geUV->GetDirectArray().Add(uv);
+ // Add this control point to the bone with weight 1.0
+ cluster->AddControlPointIndex(controlPointIdx, 1.0);
+ };
+
+ uint32_t cpIdx = 0;
+ uint32_t polyCount = mesh->GetPolygonCount();
+ for (auto tri : triangles)
+ {
+ addVert(tri.a, currentCpIdx + cpIdx + 0);
+ addVert(tri.b, currentCpIdx + cpIdx + 1);
+ addVert(tri.c, currentCpIdx + cpIdx + 2);
+
+ mesh->BeginPolygon();
+ mesh->AddPolygon(currentCpIdx + cpIdx + 0);
+ mesh->AddPolygon(currentCpIdx + cpIdx + 1);
+ mesh->AddPolygon(currentCpIdx + cpIdx + 2);
+ mesh->EndPolygon();
+ if (tri.userInfo == 0)
+ {
+ matElement->GetIndexArray().SetAt(polyCount, 0);
+ }
+ else
+ {
+ matElement->GetIndexArray().SetAt(polyCount, 1);
+ }
+ polyCount++;
+ cpIdx += 3;
+ }
+
+ mat = meshNode->EvaluateGlobalTransform();
+ cluster->SetTransformMatrix(mat);
+
+ mat = boneNode->EvaluateGlobalTransform();
+ cluster->SetTransformLinkMatrix(mat);
+
+ uint32_t addedCps = static_cast<uint32_t>(triangles.size() * 3);
+
+ for (uint32_t i = chunk->firstChildIndex; i < chunk->childIndexStop; i++)
+ {
+ addedCps += createChunkRecursive(currentCpIdx + addedCps, i, meshNode, boneNode, skin, asset, chunksGeometry);
+ }
+
+ return addedCps;
+}
+
+
+uint32_t FbxFileWriter::createChunkRecursive(uint32_t currentCpIdx, uint32_t chunkIndex, FbxNode *meshNode, FbxNode* parentNode, FbxSkin* skin, const NvBlastAsset* asset,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex)
+{
+ currentDepth++;
+
+ // if (currentDepth >= 4)
+ // {
+ // return 0;
+ // }
+
+ auto chunks = NvBlastAssetGetChunks(asset, NvBlastTkFrameworkGet()->getLogFn());
+ const NvBlastChunk* chunk = &chunks[chunkIndex];
+ physx::PxVec3 centroid = physx::PxVec3(chunk->centroid[0], chunk->centroid[1], chunk->centroid[2]);
+
+ std::ostringstream namestream;
+
+ //mesh->InitTextureUV(triangles.size() * 3);
+
+ std::ostringstream().swap(namestream); // Swap namestream with a default constructed ostringstream
+ namestream << "bone_" << chunkIndex;
+ std::string boneName = namestream.str();
+
+ FbxSkeleton* skelAttrib;
+ if (chunk->parentChunkIndex == UINT32_MAX)
+ {
+ skelAttrib = FbxSkeleton::Create(sdkManager.get(), "SkelRootAttrib");
+ skelAttrib->SetSkeletonType(FbxSkeleton::eRoot);
+
+ // Change the centroid to origin
+ centroid = physx::PxVec3(0.0f);
+ }
+ else
+ {
+ skelAttrib = FbxSkeleton::Create(sdkManager.get(), boneName.c_str());
+ skelAttrib->SetSkeletonType(FbxSkeleton::eLimbNode);
+ }
+
+ skelAttrib->Size.Set(1.0); // What's this for?
+
+
+ FbxNode* boneNode = FbxNode::Create(sdkManager.get(), boneName.c_str());
+ boneNode->SetNodeAttribute(skelAttrib);
+
+ auto mat = parentNode->EvaluateGlobalTransform().Inverse();
+
+ FbxVector4 vec(centroid.x, centroid.y, centroid.z, 0);
+ FbxVector4 c2 = mat.MultT(vec);
+
+ boneNode->LclTranslation.Set(c2);
+
+ parentNode->AddChild(boneNode);
+
+ std::ostringstream().swap(namestream); // Swap namestream with a default constructed ostringstream
+ namestream << "cluster_" << std::setw(5) << std::setfill('0') << chunkIndex;
+ std::string clusterName = namestream.str();
+
+ FbxCluster* cluster = FbxCluster::Create(sdkManager.get(), clusterName.c_str());
+ cluster->SetTransformMatrix(FbxAMatrix());
+ cluster->SetLink(boneNode);
+ cluster->SetLinkMode(FbxCluster::eTotalOne);
+
+ skin->AddCluster(cluster);
+
+ FbxMesh* mesh = static_cast<FbxMesh*>(meshNode->GetNodeAttribute());
+
+ auto geNormal = mesh->GetElementNormal();
+ auto geUV = mesh->GetElementUV("diffuseElement");
+ auto matr = mesh->GetElementMaterial();
+
+
+
+ auto posIndices = posIndex[chunkIndex];
+ auto normIndices = normIndex[chunkIndex];
+ auto uvIndices = texIndex[chunkIndex];
+ uint32_t cPolygCount = mesh->GetPolygonCount();
+ int32_t addedVertices = 0;
+ for (uint32_t subMesh = 0; subMesh < posIndices.size(); ++subMesh)
+ {
+ for (uint32_t tr = 0; tr < posIndices[subMesh].size(); tr += 3)
+ {
+ mesh->BeginPolygon();
+ mesh->AddPolygon(posIndices[subMesh][tr + 0]);
+ mesh->AddPolygon(posIndices[subMesh][tr + 1]);
+ mesh->AddPolygon(posIndices[subMesh][tr + 2]);
+ mesh->EndPolygon();
+ geNormal->GetIndexArray().SetAt(currentCpIdx + addedVertices, normIndices[subMesh][tr + 0]);
+ geNormal->GetIndexArray().SetAt(currentCpIdx + addedVertices + 1, normIndices[subMesh][tr + 1]);
+ geNormal->GetIndexArray().SetAt(currentCpIdx + addedVertices + 2, normIndices[subMesh][tr + 2]);
+
+ geUV->GetIndexArray().SetAt(currentCpIdx + addedVertices, uvIndices[subMesh][tr + 0]);
+ geUV->GetIndexArray().SetAt(currentCpIdx + addedVertices + 1, uvIndices[subMesh][tr + 1]);
+ geUV->GetIndexArray().SetAt(currentCpIdx + addedVertices + 2, uvIndices[subMesh][tr + 2]);
+ if (subMesh == 0)
+ {
+ matr->GetIndexArray().SetAt(cPolygCount, 0);
+ }
+ else
+ {
+ matr->GetIndexArray().SetAt(cPolygCount, 1);
+ }
+ cPolygCount++;
+ addedVertices += 3;
+
+ cluster->AddControlPointIndex(posIndices[subMesh][tr + 0], 1.0);
+ cluster->AddControlPointIndex(posIndices[subMesh][tr + 1], 1.0);
+ cluster->AddControlPointIndex(posIndices[subMesh][tr + 2], 1.0);
+ }
+ }
+ mat = meshNode->EvaluateGlobalTransform();
+ cluster->SetTransformMatrix(mat);
+
+ mat = boneNode->EvaluateGlobalTransform();
+ cluster->SetTransformLinkMatrix(mat);
+
+
+ for (uint32_t i = chunk->firstChildIndex; i < chunk->childIndexStop; i++)
+ {
+ addedVertices +=createChunkRecursive(addedVertices, i, meshNode, boneNode, skin, asset, posIndex, normIndex, texIndex);
+ }
+
+ return addedVertices;
+
+}
+
+void FbxFileWriter::addControlPoints(FbxMesh* mesh, const std::vector<physx::PxVec3>& pos, std::vector<std::vector<std::vector<int32_t> > >& posIndex)
+{
+ std::vector<uint32_t> vertices;
+ std::cout << "Adding control points" << std::endl;
+ std::vector<int32_t> mapping(pos.size(), -1);
+ for (uint32_t ch = 0; ch < posIndex.size(); ++ch)
+ {
+ mapping.assign(pos.size(), -1);
+ for (uint32_t sb = 0; sb < posIndex[ch].size(); ++sb)
+ {
+ for (uint32_t pi = 0; pi < posIndex[ch][sb].size(); ++pi)
+ {
+ uint32_t p = posIndex[ch][sb][pi];
+ if (mapping[p] == -1)
+ {
+ mapping[p] = (int)vertices.size();
+ vertices.push_back(posIndex[ch][sb][pi]);
+ posIndex[ch][sb][pi] = mapping[p];
+ }
+ else
+ {
+ posIndex[ch][sb][pi] = mapping[p];
+ }
+ }
+ }
+ }
+ mesh->InitControlPoints((int)vertices.size());
+ FbxVector4* controlPoints = mesh->GetControlPoints();
+ for (auto v : vertices)
+ {
+ *controlPoints = FbxVector4(pos[v].x, pos[v].y, pos[v].z, 0);
+ ++controlPoints;
+ }
+ std::cout << "Adding control points: done" << std::endl;
+}
+
+bool FbxFileWriter::finalizeFbxAndSave(FbxScene* scene, FbxSkin* skin, const std::string& outputPath, const std::string& name)
+{
+ // Store the bind pose
+
+ std::unordered_set<FbxNode*> clusterNodes;
+
+ std::function<void(FbxNode*)> addRecursively = [&](FbxNode* node)
+ {
+ if (node)
+ {
+ addRecursively(node->GetParent());
+
+ clusterNodes.insert(node);
+ }
+ };
+
+ for (uint32_t i = 0; i < (uint32_t)skin->GetClusterCount(); i++)
+ {
+ FbxNode* clusterNode = skin->GetCluster(i)->GetLink();
+
+ addRecursively(clusterNode);
+ }
+
+ NVBLAST_ASSERT(clusterNodes.size() > 0);
+
+ FbxPose* pose = FbxPose::Create(sdkManager.get(), "BasePose");
+ pose->SetIsBindPose(true);
+
+ for (auto node : clusterNodes)
+ {
+ FbxMatrix bindMat = node->EvaluateGlobalTransform();
+
+ pose->Add(node, bindMat);
+ }
+
+ scene->AddPose(pose);
+
+ FbxExporter* exporter = FbxExporter::Create(sdkManager.get(), "Scene Exporter");
+
+ auto path = outputPath + "\\" + name + ".fbx";
+
+ int lFormat;
+
+ if (bOutputFBXAscii)
+ {
+ lFormat = sdkManager->GetIOPluginRegistry()->FindWriterIDByDescription("FBX ascii (*.fbx)");
+ }
+ else
+ {
+ lFormat = sdkManager->GetIOPluginRegistry()->FindWriterIDByDescription("FBX binary (*.fbx)");
+ }
+
+ bool exportStatus = exporter->Initialize(path.c_str(), lFormat, sdkManager->GetIOSettings());
+
+ if (!exportStatus)
+ {
+ std::cerr << "Call to FbxExporter::Initialize failed" << std::endl;
+ std::cerr << "Error returned: " << exporter->GetStatus().GetErrorString() << std::endl;
+ return false;
+ }
+
+ exportStatus = exporter->Export(scene);
+
+ if (!exportStatus)
+ {
+ auto fbxStatus = exporter->GetStatus();
+
+ std::cerr << "Call to FbxExporter::Export failed" << std::endl;
+ std::cerr << "Error returned: " << fbxStatus.GetErrorString() << std::endl;
+ return false;
+ }
+
+ return true;
+}
+
+bool FbxFileWriter::saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm,
+ const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndexInp,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex,
+ const std::vector<std::string>& texPathes,
+ const uint32_t submeshCount)
+{
+ NV_UNUSED(submeshCount);
+ NV_UNUSED(texPathes);
+
+ auto posIndex = posIndexInp;
+ /**
+ Get polygon count
+ */
+ uint32_t polygCount = 0;
+ for (uint32_t ch = 0; ch < posIndex.size(); ++ch)
+ {
+ for (uint32_t sbm = 0; sbm < posIndex[ch].size(); ++sbm)
+ {
+ polygCount += (uint32_t)posIndex[ch][sbm].size() / 3;
+ }
+ }
+
+ FbxIOSettings* ios = FbxIOSettings::Create(sdkManager.get(), IOSROOT);
+ // Set some properties on the io settings
+
+ // ios->SetBoolProp(EXP_ASCIIFBX, true);
+
+ sdkManager->SetIOSettings(ios);
+
+ sdkManager->GetIOSettings()->SetBoolProp(EXP_ASCIIFBX, bOutputFBXAscii);
+
+ FbxScene* scene = FbxScene::Create(sdkManager.get(), "Export Scene");
+
+ if (getConvertToUE4())
+ {
+ FbxAxisSystem::EFrontVector FrontVector = (FbxAxisSystem::EFrontVector) - FbxAxisSystem::eParityOdd;
+ const FbxAxisSystem UnrealZUp(FbxAxisSystem::eZAxis, FrontVector, FbxAxisSystem::eRightHanded);
+
+ scene->GetGlobalSettings().SetAxisSystem(UnrealZUp);
+ }
+
+ // Otherwise default to Maya defaults
+
+ FbxMesh* mesh = FbxMesh::Create(sdkManager.get(), "meshgeo");
+
+ FbxGeometryElementNormal* geNormal = mesh->CreateElementNormal();
+ geNormal->SetMappingMode(FbxGeometryElement::eByPolygonVertex);
+ geNormal->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
+
+ FbxGeometryElementUV* geUV = mesh->CreateElementUV("diffuseElement");
+ geUV->SetMappingMode(FbxGeometryElement::eByPolygonVertex);
+ geUV->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
+
+
+ FbxNode* meshNode = FbxNode::Create(scene, "meshnode");
+ meshNode->SetNodeAttribute(mesh);
+ meshNode->SetShadingMode(FbxNode::eTextureShading);
+
+ FbxNode* lRootNode = scene->GetRootNode();
+ lRootNode->AddChild(meshNode);
+
+ FbxSkin* skin = FbxSkin::Create(sdkManager.get(), "Skin of the thing");
+ skin->SetGeometry(mesh);
+
+ mesh->AddDeformer(skin);
+
+ /**
+ Create control points, copy data to buffers
+ */
+ addControlPoints(mesh, pos, posIndex);
+
+ auto normalsElem = mesh->GetElementNormal();
+ for (uint32_t i = 0; i < norm.size(); ++i)
+ {
+ normalsElem->GetDirectArray().Add(FbxVector4(norm[i].x, norm[i].y, norm[i].z, 0));
+ }
+ auto uvsElem = mesh->GetElementUV("diffuseElement");
+ for (uint32_t i = 0; i < uvs.size(); ++i)
+ {
+ uvsElem->GetDirectArray().Add(FbxVector2(uvs[i].x, uvs[i].y));
+ }
+
+
+ // Add a material otherwise UE4 freaks out on import
+
+ FbxGeometryElementMaterial* matElement = mesh->CreateElementMaterial();
+ matElement->SetMappingMode(FbxGeometryElement::eByPolygon);
+ matElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
+
+ FbxSurfacePhong* material = FbxSurfacePhong::Create(sdkManager.get(), "FirstExportMaterial");
+
+ material->Diffuse.Set(FbxDouble3(1.0, 1.0, 0));
+ material->DiffuseFactor.Set(1.0);
+
+ meshNode->AddMaterial(material);
+
+ FbxSurfacePhong* material2 = FbxSurfacePhong::Create(sdkManager.get(), "SecondExportMaterial");
+
+ material2->Diffuse.Set(FbxDouble3(1.0, 0.0, 1.0));
+ material2->DiffuseFactor.Set(1.0);
+
+ meshNode->AddMaterial(material2);
+
+
+ matElement->GetIndexArray().SetCount(polygCount);
+ normalsElem->GetIndexArray().SetCount(polygCount * 3);
+ uvsElem->GetIndexArray().SetCount(polygCount * 3);
+
+
+ std::cout << "Create chunks recursive" << std::endl;
+
+ // Now walk the tree and create a skeleton with geometry at the same time
+ // Find a "root" chunk and walk the tree from there.
+ uint32_t chunkCount = NvBlastAssetGetChunkCount(asset, NvBlastTkFrameworkGet()->getLogFn());
+ auto chunks = NvBlastAssetGetChunks(asset, NvBlastTkFrameworkGet()->getLogFn());
+ currentDepth = 0;
+ uint32_t cpIdx = 0;
+ for (uint32_t i = 0; i < chunkCount; i++)
+ {
+ const NvBlastChunk* chunk = &chunks[i];
+
+ if (chunk->parentChunkIndex == UINT32_MAX)
+ {
+ uint32_t addedCps = createChunkRecursive(cpIdx, i, meshNode, lRootNode, skin, asset, posIndex, normIndex, texIndex);
+ cpIdx += addedCps;
+ }
+ }
+
+ return finalizeFbxAndSave(scene, skin, outputPath, name);
+}
+
+bool FbxFileWriter::saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm, const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& indices)
+{
+ std::vector<std::string> matnames;
+ matnames.push_back("");
+ return saveToFile(asset, name, outputPath, pos, norm, uvs, indices, indices, indices, matnames, 1);
+} \ No newline at end of file
diff --git a/NvBlast/tools/common/FbxFileWriter.h b/NvBlast/tools/common/FbxFileWriter.h
new file mode 100644
index 0000000..646d1ec
--- /dev/null
+++ b/NvBlast/tools/common/FbxFileWriter.h
@@ -0,0 +1,49 @@
+#pragma once
+#include "IMeshFileWriter.h"
+#include "fbxsdk.h"
+#include <memory>
+
+struct NvBlastAsset;
+
+class FbxFileWriter : public IMeshFileWriter
+{
+public:
+
+ FbxFileWriter();
+ ~FbxFileWriter() = default;
+
+ virtual bool saveToFile(const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry, std::string assetName, std::string outputPath) override;
+
+
+
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm,
+ const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex,
+ const std::vector<std::string>& texPathes,
+ const uint32_t submeshCount) override;
+
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm, const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& indices) override;
+
+ bool bOutputFBXAscii;
+
+private:
+
+ uint32_t currentDepth;
+
+ std::shared_ptr<FbxManager> sdkManager;
+
+ uint32_t createChunkRecursive(uint32_t currentCpIdx, uint32_t chunkIndex, FbxNode *meshNode, FbxNode* parentNode, FbxSkin* skin, const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry);
+
+ uint32_t createChunkRecursive(uint32_t currentCpIdx, uint32_t chunkIndex, FbxNode *meshNode, FbxNode* parentNode, FbxSkin* skin, const NvBlastAsset* asset,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex);
+
+ void addControlPoints(FbxMesh* mesh, const std::vector<physx::PxVec3>& pos, std::vector<std::vector<std::vector<int32_t> > >& posIndex);
+
+ bool finalizeFbxAndSave(FbxScene* scene, FbxSkin* skin, const std::string& outputPath, const std::string& name);
+
+};
diff --git a/NvBlast/tools/common/FbxUtils.cpp b/NvBlast/tools/common/FbxUtils.cpp
new file mode 100644
index 0000000..b0bd94b
--- /dev/null
+++ b/NvBlast/tools/common/FbxUtils.cpp
@@ -0,0 +1,30 @@
+#include "fbxsdk.h"
+#include "FbxUtils.h"
+#include "PxVec3.h"
+#include "PxVec2.h"
+#include "NvBlastExtAuthoringTypes.h"
+
+using physx::PxVec3;
+using physx::PxVec2;
+
+
+void FbxUtils::VertexToFbx(Nv::Blast::Vertex& vert, FbxVector4& outVertex, FbxVector4& outNormal, FbxVector2& outUV)
+{
+ PxVec3ToFbx(vert.p, outVertex);
+ PxVec3ToFbx(vert.n, outNormal);
+ PxVec2ToFbx(vert.uv[0], outUV);
+}
+
+void FbxUtils::PxVec3ToFbx(physx::PxVec3& inVector, FbxVector4& outVector)
+{
+ outVector[0] = inVector.x;
+ outVector[1] = inVector.y;
+ outVector[2] = inVector.z;
+ outVector[3] = 0;
+}
+
+void FbxUtils::PxVec2ToFbx(physx::PxVec2& inVector, FbxVector2& outVector)
+{
+ outVector[0] = inVector.x;
+ outVector[1] = inVector.y;
+}
diff --git a/NvBlast/tools/common/FbxUtils.h b/NvBlast/tools/common/FbxUtils.h
new file mode 100644
index 0000000..902af21
--- /dev/null
+++ b/NvBlast/tools/common/FbxUtils.h
@@ -0,0 +1,20 @@
+#pragma once
+#include "PxVec3.h"
+#include "PxVec2.h"
+
+namespace Nv
+{
+ namespace Blast
+ {
+ struct Vertex;
+ }
+}
+
+class FbxUtils
+{
+public:
+ static void VertexToFbx(Nv::Blast::Vertex& vert, FbxVector4& outVertex, FbxVector4& outNormal, FbxVector2& outUV);
+
+ static void PxVec3ToFbx(physx::PxVec3& inVector, FbxVector4& outVector);
+ static void PxVec2ToFbx(physx::PxVec2& inVector, FbxVector2& outVector);
+};
diff --git a/NvBlast/tools/common/IMeshFileReader.h b/NvBlast/tools/common/IMeshFileReader.h
new file mode 100644
index 0000000..d632a2f
--- /dev/null
+++ b/NvBlast/tools/common/IMeshFileReader.h
@@ -0,0 +1,21 @@
+#pragma once
+#include <memory>
+#include <string>
+#include "NvBlastExtAuthoringMesh.h"
+
+
+class IMeshFileReader
+{
+public:
+
+ /*
+ Load from the specified file path, returning a mesh or nullptr if failed
+ */
+ virtual std::shared_ptr<Nv::Blast::Mesh> loadFromFile(std::string filename) = 0;
+
+ virtual bool getConvertToUE4() { return bConvertToUE4; }
+ virtual void setConvertToUE4(bool bConvert) { bConvertToUE4 = bConvert; }
+
+private:
+ bool bConvertToUE4;
+}; \ No newline at end of file
diff --git a/NvBlast/tools/common/IMeshFileWriter.h b/NvBlast/tools/common/IMeshFileWriter.h
new file mode 100644
index 0000000..562df70
--- /dev/null
+++ b/NvBlast/tools/common/IMeshFileWriter.h
@@ -0,0 +1,41 @@
+#pragma once
+#include "NvBlastExtAuthoringTypes.h"
+#include <string>
+#include <memory>
+#include <vector>
+
+struct NvBlastAsset;
+
+class IMeshFileWriter
+{
+public:
+
+ /**
+ Input mesh geometry as triangle array
+ */
+ virtual bool saveToFile(const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry, std::string assetName, std::string outputPath) = 0;
+
+
+ /**
+ Input mesh geometry as vertex buffers with separate indices for positions, normals and uvs. Is used for compressed output to .obj file.
+ */
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm,
+ const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex,
+ const std::vector<std::string>& texPathes,
+ const uint32_t submeshCount) = 0;
+
+
+ /**
+ Input mesh geometry as vertex buffers and single index array.
+ */
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm, const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& indices) = 0;
+
+ virtual bool getConvertToUE4() { return bConvertToUE4; }
+ virtual void setConvertToUE4(bool bConvert) { bConvertToUE4 = bConvert; }
+private:
+ bool bConvertToUE4;
+};
diff --git a/NvBlast/tools/common/Log.cpp b/NvBlast/tools/common/Log.cpp
new file mode 100644
index 0000000..4371a7d
--- /dev/null
+++ b/NvBlast/tools/common/Log.cpp
@@ -0,0 +1,59 @@
+#include "Log.h"
+
+#include "PsString.h"
+
+#include <iomanip>
+#include <stdarg.h>
+#include <stdio.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+namespace Nv
+{
+namespace Blast
+{
+
+void fLogf(const char* format, ...)
+{
+ char buf[4096], *p = buf;
+ va_list args;
+ int n;
+
+ va_start(args, format);
+ //n = _vsnprintf(p, sizeof buf - 3, format, args);
+ n = vsprintf_s(p, sizeof(buf)-3, format, args);
+ va_end(args);
+
+ p += (n < 0) ? sizeof buf - 3 : n;
+
+ while (p > buf && isspace((unsigned char)p[-1]))
+ {
+ *--p = '\0';
+ }
+
+ *p++ = '\r';
+ *p++ = '\n';
+ *p = '\0';
+
+ fLog(buf, Log::TYPE_INFO);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+void Log::flushDeferredMessages()
+{
+ if (mDeferredMessages.size() == 0) return;
+
+ std::cout << std::endl;
+ for (std::vector<std::string>::iterator it = mDeferredMessages.begin(); it != mDeferredMessages.end(); ++it)
+ {
+ log(*it, mMinVerbosity);
+ }
+ mDeferredMessages.clear();
+}
+
+
+
+} // namespace Blast
+} // namespace Nv
diff --git a/NvBlast/tools/common/Log.h b/NvBlast/tools/common/Log.h
new file mode 100644
index 0000000..528a3b9
--- /dev/null
+++ b/NvBlast/tools/common/Log.h
@@ -0,0 +1,122 @@
+#ifndef LOG_H
+#define LOG_H
+
+#include "Utils.h"
+#include "PxVec3.h"
+
+#include <sstream>
+
+namespace Nv
+{
+namespace Blast
+{
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+void fLogf(const char* format, ...);
+
+class Log : public Singleton<Log>
+{
+ friend class Singleton<Log>;
+
+public:
+
+ enum MessageType {
+ TYPE_INFO = 0,
+ TYPE_WARNING,
+ TYPE_ERROR,
+ TYPE_DEFERRED,
+
+ NUM_TYPES,
+ MOST_VERBOSE = TYPE_INFO,
+ LEAST_VERBOSE = TYPE_ERROR
+#if defined(_DEBUG)
+ , DEFAULT_VERBOSITY = MOST_VERBOSE
+#else
+ , DEFAULT_VERBOSITY = LEAST_VERBOSE
+#endif
+ };
+ typedef MessageType Verbosity;
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ template<typename T>
+ Log& log(const T& value, MessageType messageType);
+
+ void flushDeferredMessages();
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ void setCurrentVerbosity(Verbosity verbosity) { mCurrentVerbosity = verbosity; }
+ Verbosity getCurrentVerbosity() const { return mCurrentVerbosity; }
+
+ // Messages types below this level will be ignored
+ void setMinVerbosity(Verbosity verbosity) { mMinVerbosity = verbosity; }
+ Verbosity getMinVerbosity() const { return mMinVerbosity; }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+protected:
+ Log(MessageType verbosity = DEFAULT_VERBOSITY)
+ : mCurrentVerbosity(LEAST_VERBOSE),
+ mMinVerbosity(verbosity) { }
+
+private:
+ Verbosity mCurrentVerbosity;
+ Verbosity mMinVerbosity;
+ std::vector<std::string> mDeferredMessages;
+};
+
+///////////////////////////////////////////////////////////////////////////
+
+PX_INLINE std::ostream& operator<< (std::ostream& stream, const physx::PxVec3& vec)
+{
+ return stream << "(" << vec.x << ", " << vec.y << ", " << vec.z << ")";
+}
+
+template<typename T>
+Log& Log::log(const T& value, Log::MessageType messageType)
+{
+ if (TYPE_DEFERRED == messageType)
+ {
+ std::stringstream ss;
+ ss << value;
+ mDeferredMessages.push_back(ss.str());
+ }
+ else if(mMinVerbosity <= messageType)
+ {
+ std::cout << value;
+ }
+ return *this;
+}
+
+PX_INLINE Log& lout() { return Log::instance(); }
+
+template <typename T>
+PX_INLINE void fLog(const T& value, Log::MessageType messageType = Log::TYPE_INFO)
+{
+ lout().log<T>(value, messageType);
+}
+template <typename T>
+PX_INLINE Log& operator<<(Log& logger, const T& value)
+{
+ return logger.log<T>(value, logger.getCurrentVerbosity());
+}
+PX_INLINE Log& operator<<(Log& logger, Log::MessageType verbosity)
+{
+ logger.setCurrentVerbosity(verbosity);
+ return logger;
+}
+typedef std::ostream& (*ostream_manipulator)(std::ostream&);
+PX_INLINE Log& operator<<(Log& logger, ostream_manipulator pf)
+{
+ return operator<< <ostream_manipulator> (logger, pf);
+}
+
+
+} // namespace Blast
+} // namespace Nv
+
+
+#endif
diff --git a/NvBlast/tools/common/ObjFileReader.cpp b/NvBlast/tools/common/ObjFileReader.cpp
new file mode 100644
index 0000000..30b4c5c
--- /dev/null
+++ b/NvBlast/tools/common/ObjFileReader.cpp
@@ -0,0 +1,63 @@
+#include "ObjFileReader.h"
+
+#pragma warning(push)
+#pragma warning(disable:4706)
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "tiny_obj_loader.h"
+#pragma warning(pop)
+
+
+#include <iostream>
+#include "PxVec3.h"
+#include "PxVec2.h"
+
+using physx::PxVec3;
+using physx::PxVec2;
+using Nv::Blast::Mesh;
+
+
+ObjFileReader::ObjFileReader()
+{
+ setConvertToUE4(false);
+}
+
+std::shared_ptr<Nv::Blast::Mesh> ObjFileReader::loadFromFile(std::string filename)
+{
+ std::vector<tinyobj::shape_t> shapes;
+ std::vector<tinyobj::material_t> mats;
+ std::string err;
+ std::string mtlPath;
+ bool ret = tinyobj::LoadObj(shapes, mats, err, filename.c_str());
+ // can't load?
+ if (!ret)
+ return nullptr;
+
+ if (shapes.size() > 1)
+ {
+ std::cout << "Can load only one object per mesh" << std::endl;
+ }
+
+ std::vector<PxVec3> positions;
+ std::vector<PxVec3> normals;
+ std::vector<PxVec2> uv;
+
+ auto& psVec = shapes[0].mesh.positions;
+ for (uint32_t i = 0; i < psVec.size() / 3; ++i)
+ {
+ positions.push_back(PxVec3(psVec[i * 3], psVec[i * 3 + 1], psVec[i * 3 + 2]));
+ }
+ auto& nmVec = shapes[0].mesh.normals;
+ for (uint32_t i = 0; i < nmVec.size() / 3; ++i)
+ {
+ normals.push_back(PxVec3(nmVec[i * 3], nmVec[i * 3 + 1], nmVec[i * 3 + 2]));
+ }
+ auto& txVec = shapes[0].mesh.texcoords;
+ for (uint32_t i = 0; i < txVec.size() / 2; ++i)
+ {
+ uv.push_back(PxVec2(txVec[i * 2], txVec[i * 2 + 1]));
+ }
+ PxVec3* nr = (!normals.empty()) ? normals.data() : 0;
+ PxVec2* uvp = (!uv.empty()) ? uv.data() : 0;
+
+ return std::make_shared<Mesh>(positions.data(), nr, uvp, static_cast<uint32_t>(positions.size()), shapes[0].mesh.indices.data(), static_cast<uint32_t>(shapes[0].mesh.indices.size()));
+}
diff --git a/NvBlast/tools/common/ObjFileReader.h b/NvBlast/tools/common/ObjFileReader.h
new file mode 100644
index 0000000..acc85ba
--- /dev/null
+++ b/NvBlast/tools/common/ObjFileReader.h
@@ -0,0 +1,16 @@
+#pragma once
+#include <memory>
+#include "IMeshFileReader.h"
+
+class ObjFileReader: public IMeshFileReader
+{
+public:
+ ObjFileReader();
+ ~ObjFileReader() = default;
+
+ /*
+ Load from the specified file path, returning a mesh or nullptr if failed
+ */
+ std::shared_ptr<Nv::Blast::Mesh> loadFromFile(std::string filename);
+
+};
diff --git a/NvBlast/tools/common/ObjFileWriter.cpp b/NvBlast/tools/common/ObjFileWriter.cpp
new file mode 100644
index 0000000..94aad7d
--- /dev/null
+++ b/NvBlast/tools/common/ObjFileWriter.cpp
@@ -0,0 +1,142 @@
+#include "ObjFileWriter.h"
+#include <PxVec3.h>
+#include <sstream>
+
+using namespace physx;
+using namespace Nv::Blast;
+
+bool ObjFileWriter::saveToFile(const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry, std::string assetName, std::string outputPath)
+{
+ NV_UNUSED(asset);
+
+ std::vector<PxVec3> pos;
+ std::vector<PxVec3> norm;
+ std::vector<PxVec2> tex;
+ for (uint32_t vc = 0; vc < chunksGeometry.size(); ++vc)
+ {
+ std::vector<Triangle>& chunk = chunksGeometry[vc];
+ for (uint32_t i = 0; i < chunk.size(); ++i)
+ {
+ pos.push_back(chunk[i].a.p);
+ pos.push_back(chunk[i].b.p);
+ pos.push_back(chunk[i].c.p);
+
+ norm.push_back(chunk[i].a.n);
+ norm.push_back(chunk[i].b.n);
+ norm.push_back(chunk[i].c.n);
+
+ tex.push_back(chunk[i].a.uv[0]);
+ tex.push_back(chunk[i].b.uv[0]);
+ tex.push_back(chunk[i].c.uv[0]);
+ }
+ }
+ std::vector < std::vector<std::vector<int32_t> > > indices(chunksGeometry.size());
+ int32_t index = 0;
+ for (uint32_t vc = 0; vc < chunksGeometry.size(); ++vc)
+ {
+ indices[vc].push_back(std::vector<int32_t>());
+ for (uint32_t i = 0; i < chunksGeometry[vc].size() * 3; ++i)
+ {
+ indices[vc][0].push_back(index);
+ index++;
+ }
+ }
+
+ return saveToFile(asset, assetName, outputPath, pos, norm, tex, indices);
+}
+
+bool ObjFileWriter::saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm,
+ const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex,
+ const std::vector<std::string>& texPathes,
+ const uint32_t submeshCount)
+{
+ NV_UNUSED(asset);
+
+ uint32_t chunkCount = static_cast<uint32_t>(posIndex.size());
+ if (posIndex.size() != normIndex.size() || normIndex.size() != texIndex.size())
+ {
+ return false;
+ }
+
+ // export materials (mtl file)
+ {
+ std::ostringstream mtlFilePath;
+ mtlFilePath << outputPath << "\\" << name << ".mtl";
+ FILE* f = fopen(mtlFilePath.str().c_str(), "w");
+ if (!f)
+ return false;
+
+ for (uint32_t submeshIndex = 0; submeshIndex < submeshCount; ++submeshIndex)
+ {
+ fprintf(f, "newmtl mat%d\n", submeshIndex);
+ fprintf(f, "\tmap_Kd %s\n", texPathes[submeshIndex].data());
+ fprintf(f, "\n");
+ }
+
+ fclose(f);
+ }
+
+ /// Export geometry to *.obj file
+ {
+ std::ostringstream objFilePath;
+ objFilePath << outputPath << "\\" << name << ".obj";
+ FILE* f = fopen(objFilePath.str().c_str(), "w");
+ if (!f)
+ return false;
+
+ fprintf(f, "mtllib %s.mtl\n", name.c_str());
+ fprintf(f, "o frac \n");
+
+
+ /// Write compressed vertices
+ for (uint32_t i = 0; i < pos.size(); ++i)
+ {
+ fprintf(f, "v %.4f %.4f %.4f\n", pos[i].x, pos[i].y, pos[i].z);
+ }
+ for (uint32_t i = 0; i < norm.size(); ++i)
+ {
+ fprintf(f, "vn %.4f %.4f %.4f\n", norm[i].x, norm[i].y, norm[i].z);
+ }
+ for (uint32_t i = 0; i < uvs.size(); ++i)
+ {
+ fprintf(f, "vt %.4f %.4f\n", uvs[i].y, uvs[i].x);
+ }
+
+ for (uint32_t chunkIndex = 0; chunkIndex < chunkCount; ++chunkIndex)
+ {
+ for (uint32_t submeshIndex = 0; submeshIndex < posIndex[chunkIndex].size(); ++submeshIndex)
+ {
+ fprintf(f, "g %d_%d \n", chunkIndex, submeshIndex);
+ fprintf(f, "usemtl mat%d\n", submeshIndex);
+ uint32_t indexCount = static_cast<uint32_t>(posIndex[chunkIndex][submeshIndex].size());
+ const std::vector<int32_t>& pI = posIndex[chunkIndex][submeshIndex];
+ const std::vector<int32_t>& nI = normIndex[chunkIndex][submeshIndex];
+ const std::vector<int32_t>& tI = texIndex[chunkIndex][submeshIndex];
+
+ for (uint32_t i = 0; i < indexCount;)
+ {
+ fprintf(f, "f %d/%d/%d ", pI[i] + 1, tI[i] + 1, nI[i] + 1);
+ ++i;
+ fprintf(f, "%d/%d/%d ", pI[i] + 1, tI[i] + 1, nI[i] + 1);
+ ++i;
+ fprintf(f, "%d/%d/%d\n", pI[i] + 1, tI[i] + 1, nI[i] + 1);
+ ++i;
+ }
+ }
+ }
+ fclose(f);
+ }
+ return true;
+
+}
+
+bool ObjFileWriter::saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm, const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& indices)
+{
+ std::vector<std::string> matnames;
+ matnames.push_back("");
+ return saveToFile(asset, name, outputPath, pos, norm, uvs, indices, indices, indices, matnames, 1);
+} \ No newline at end of file
diff --git a/NvBlast/tools/common/ObjFileWriter.h b/NvBlast/tools/common/ObjFileWriter.h
new file mode 100644
index 0000000..547ba62
--- /dev/null
+++ b/NvBlast/tools/common/ObjFileWriter.h
@@ -0,0 +1,26 @@
+#pragma once
+#include "IMeshFileWriter.h"
+#include <memory>
+
+struct NvBlastAsset;
+
+class ObjFileWriter : public IMeshFileWriter
+{
+public:
+
+ ObjFileWriter() {};
+ ~ObjFileWriter() = default;
+
+ virtual bool saveToFile(const NvBlastAsset* asset, std::vector<std::vector<Nv::Blast::Triangle>> chunksGeometry, std::string assetName, std::string outputPath) override;
+
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm,
+ const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& posIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& normIndex,
+ const std::vector<std::vector<std::vector<int32_t> > >& texIndex,
+ const std::vector<std::string>& texPathes,
+ const uint32_t submeshCount) override;
+
+ virtual bool saveToFile(const NvBlastAsset* asset, const std::string& name, const std::string& outputPath, const std::vector<physx::PxVec3>& pos, const std::vector<physx::PxVec3>& norm, const std::vector<physx::PxVec2>& uvs,
+ const std::vector<std::vector<std::vector<int32_t> > >& indices) override;
+};
diff --git a/NvBlast/tools/common/Utils.cpp b/NvBlast/tools/common/Utils.cpp
new file mode 100644
index 0000000..736159e
--- /dev/null
+++ b/NvBlast/tools/common/Utils.cpp
@@ -0,0 +1,166 @@
+#include "Utils.h"
+
+#include "Log.h"
+
+#include <string.h>
+
+#if PX_WINDOWS_FAMILY
+#include <direct.h>
+#define getCwd _getcwd
+#else
+#include <unistd.h>
+#define getCwd getcwd
+#endif
+
+///////////////////////////////////////////////////////////////////////////
+
+namespace Nv
+{
+namespace Blast
+{
+
+//////////////////////////////////////////////////////////////////////////////
+
+static void addSlashToPath(std::string& path)
+{
+ if (path[path.length() - 1] != '/' && path[path.length() - 1] != '\\')
+ {
+ path.append("/");
+ }
+}
+
+FileUtils::FileUtils()
+{
+ mSearchPaths.push_back("");
+ char currentPathTemp[FILENAME_MAX];
+ if (getCwd(currentPathTemp, sizeof(currentPathTemp)))
+ {
+ std::string currentPath(currentPathTemp);
+ addSlashToPath(currentPath);
+ addAbsolutePath(currentPath);
+ mCurrentPath = currentPath;
+ }
+}
+
+std::string FileUtils::getDirectory(const std::string& filePath)
+{
+ return filePath.substr(0, filePath.find_last_of("/\\") + 1);
+}
+
+std::string FileUtils::getFilename(const std::string& filePath, bool bWithExtension)
+{
+ size_t p0 = filePath.find_last_of("/\\") + 1;
+ if (bWithExtension)
+ {
+ return filePath.substr(p0);
+ }
+ else
+ {
+ return filePath.substr(p0, filePath.find_last_of(".") - p0);
+ }
+}
+
+std::string FileUtils::getFileExtension(const std::string& filePath)
+{
+ std::string filename = getFilename(filePath);
+ size_t p0 = filename.find_last_of(".");
+ if (p0 != std::string::npos)
+ return filePath.substr(p0);// + 1);
+ return "";
+}
+
+void FileUtils::addAbsolutePath(const std::string& path)
+{
+ if (path.empty())
+ {
+ return;
+ }
+
+ std::string newPath = path;
+ addSlashToPath(newPath);
+
+ mSearchPaths.push_back(newPath);
+}
+
+void FileUtils::addRelativePath(const std::string& relPath)
+{
+ addAbsolutePath(mCurrentPath + relPath);
+}
+
+void FileUtils::clearPaths()
+{
+ mSearchPaths.clear();
+}
+
+FILE* FileUtils::findFile(const std::string& path, bool bVerbose)
+{
+ FILE* file;
+ if (find(path, &file, NULL, bVerbose))
+ {
+ return file;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+std::string FileUtils::findPath(const std::string& path, bool bVerbose)
+{
+ std::string fullPath;
+ if (find(path, NULL, &fullPath, bVerbose))
+ {
+ return fullPath;
+ }
+ else
+ {
+ return path;
+ }
+}
+
+bool FileUtils::find(const std::string& path, FILE** ppFile, std::string* pFullPath, bool bVerbose)
+{
+ if (mSearchPaths.empty() || path.empty())
+ {
+ if (bVerbose)
+ {
+ lout() << Log::TYPE_ERROR << "Error: Invalid search path configuration.";
+ }
+ return false;
+ }
+
+ std::string fullPath;
+
+ FILE* file = NULL;
+ const uint32_t numSearchPaths = (uint32_t)mSearchPaths.size();
+ for (uint32_t i = 0; i < numSearchPaths; ++i)
+ {
+ fullPath = mSearchPaths[i] + path;
+ fopen_s(&file, fullPath.c_str(), "rb");
+ if (file)
+ {
+ break;
+ }
+ }
+
+ if (!file)
+ {
+ if (bVerbose)
+ lout() << Log::TYPE_ERROR << std::endl << "Error: Unable to find file " << path << std::endl;
+ return false;
+ }
+
+ if (ppFile)
+ *ppFile = file;
+ else
+ fclose(file);
+
+ if (pFullPath)
+ *pFullPath = fullPath;
+
+ return true;
+}
+
+
+} // namespace Blast
+} // namespace Nv
diff --git a/NvBlast/tools/common/Utils.h b/NvBlast/tools/common/Utils.h
new file mode 100644
index 0000000..26299e8
--- /dev/null
+++ b/NvBlast/tools/common/Utils.h
@@ -0,0 +1,113 @@
+#ifndef UTILS_H
+#define UTILS_H
+
+#include "PsString.h"
+
+#include <string>
+#include <iostream>
+#include <vector>
+#include <map>
+
+//////////////////////////////////////////////////////////////////////////////
+
+//////////////////////////////////////////////////////////////////////////////
+
+namespace Nv
+{
+namespace Blast
+{
+
+///////////////////////////////////////////////////////////////////////////
+
+template<class T>
+PX_INLINE bool isNull(const T* p) { return nullptr == p; }
+
+template<class Releasable, class Releaser> class ScopedResource;
+template<class Releasable, class Releaser>
+PX_INLINE bool isNull(const ScopedResource<Releasable,Releaser>& p) { return !p; }
+
+PX_INLINE bool isNullString(const char* pString)
+{
+ return (nullptr == pString || pString[0] == '\0' || physx::shdfnd::strcmp(pString, "null") == 0);
+}
+
+template<class T>
+PX_INLINE bool isValid(const T& p) { return !isNull(p.get()); }
+
+PX_INLINE bool isValidString(const char* pString) { return !isNullString(pString); }
+
+///////////////////////////////////////////////////////////////////////////
+
+// Note: This is not a thread safe singleton class
+template <class T>
+class Singleton
+{
+ // The fact that I cannot declare T a friend directly is rather absurd...
+ typedef T Type;
+ friend typename Singleton<T>::Type;
+
+ //////////////////////////////////////////////////////////////////////////////
+
+public:
+ static T& instance()
+ {
+ static T _instance;
+ return _instance;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+private:
+ Singleton() { }
+ ~Singleton() { };
+ Singleton(const Singleton&);
+ Singleton& operator=(const Singleton&);
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+class FileUtils : public Singleton<FileUtils>
+{
+ friend class Singleton<FileUtils>;
+
+public:
+ void addAbsolutePath(const std::string&);
+ void addRelativePath(const std::string&);
+ void clearPaths();
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ FILE* findFile(const std::string&, bool bVerbose = true);
+ std::string findPath(const std::string&, bool bVerbose = true);
+ bool find(const std::string&, FILE**, std::string*, bool bVerbose = true);
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ const std::string& getCurrentPath() const
+ {
+ return mCurrentPath;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ static std::string getDirectory(const std::string&);
+ static std::string getFilename(const std::string&, bool bWithExtension = true);
+ static std::string getFileExtension(const std::string&);
+
+ //////////////////////////////////////////////////////////////////////////////
+
+protected:
+ FileUtils();
+
+ //////////////////////////////////////////////////////////////////////////////
+
+ std::string mCurrentPath;
+ std::vector<std::string> mSearchPaths;
+};
+
+
+} // namespace Blast
+} // namespace Nv
+
+
+#endif