aboutsummaryrefslogtreecommitdiff
path: root/sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp')
-rwxr-xr-x[-rw-r--r--]sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp1272
1 files changed, 636 insertions, 636 deletions
diff --git a/sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp b/sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp
index edf6c16..99600fd 100644..100755
--- a/sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp
+++ b/sdk/extensions/exporter/source/NvBlastExtExporterFbxReader.cpp
@@ -1,636 +1,636 @@
-// This code contains NVIDIA Confidential Information and is disclosed to you
-// under a form of NVIDIA software license agreement provided separately to you.
-//
-// Notice
-// NVIDIA Corporation and its licensors retain all intellectual property and
-// proprietary rights in and to this software and related documentation and
-// any modifications thereto. Any use, reproduction, disclosure, or
-// distribution of this software and related documentation without an express
-// license agreement from NVIDIA Corporation is strictly prohibited.
-//
-// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
-// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
-// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
-// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
-//
-// Information and code furnished is believed to be accurate and reliable.
-// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
-// information or for any infringement of patents or other rights of third parties that may
-// result from its use. No license is granted by implication or otherwise under any patent
-// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
-// This code supersedes and replaces all information previously supplied.
-// NVIDIA Corporation products are not authorized for use as critical
-// components in life support devices or systems without express written approval of
-// NVIDIA Corporation.
-//
-// Copyright (c) 2018 NVIDIA Corporation. All rights reserved.
-
-#include "NvBlastExtExporterFbxReader.h"
-#include "NvBlastExtExporterFbxUtils.h"
-#include "NvBlastGlobals.h"
-#include <fbxsdk.h>
-#include <iostream>
-#include <algorithm>
-#include <cctype>
-#include <sstream>
-#include <unordered_map>
-
-#include "PxVec3.h"
-#include "PxVec2.h"
-#include "PxPlane.h"
-#include "NvBlastExtAuthoringMesh.h"
-#include "NvBlastExtAuthoringBondGenerator.h"
-#include "NvBlastExtAuthoringCollisionBuilder.h"
-
-using physx::PxVec3;
-using physx::PxVec2;
-
-using namespace Nv::Blast;
-
-FbxFileReader::FbxFileReader()
-{
- mMeshCount = 0;
- mChunkCount = 0;
-}
-
-void FbxFileReader::release()
-{
- delete this;
-}
-
-FbxAMatrix FbxFileReader::getTransformForNode(FbxNode* node)
-{
- //The geometry transform contains the information about the pivots in the mesh node relative to the node's transform
- FbxAMatrix geometryTransform(node->GetGeometricTranslation(FbxNode::eSourcePivot),
- node->GetGeometricRotation(FbxNode::eSourcePivot),
- node->GetGeometricScaling(FbxNode::eSourcePivot));
- FbxAMatrix nodeTransform = node->EvaluateGlobalTransform();
-
- return nodeTransform * geometryTransform;
-}
-
-void FbxFileReader::loadFromFile(const char* 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)
- {
- manager->Destroy();
- });
-
- mChunkCount = 0;
- mCollisionNodes.clear();
- 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, -1, sdkManager->GetIOSettings());
-
- if (!importStatus)
- {
- std::cerr << "Call to FbxImporter::Initialize failed." << std::endl;
- std::cerr << "Error returned: " << importer->GetStatus().GetErrorString() << std::endl;
-
- return;
- }
-
- 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;
- }
-
- int32_t matCount = scene->GetMaterialCount();
-
- for (int32_t i = 0; i < matCount; ++i)
- {
- mMaterialNames.push_back(std::string(scene->GetMaterial(i)->GetName()));
- }
-
- //This removes axis and unit conversion nodes so it converts the entire scene to the header specified axis and units
- FbxRootNodeUtility::RemoveAllFbxRoots(scene);
-
- FbxAxisSystem blastAxisSystem = FbxUtils::getBlastFBXAxisSystem();
- FbxAxisSystem sourceSetup = scene->GetGlobalSettings().GetAxisSystem();
- if (sourceSetup != blastAxisSystem)
- {
- std::cout << "Converting to Blast coordinates" << std::endl;
- std::cout << "Existing axis: " << FbxUtils::FbxAxisSystemToString(sourceSetup) << std::endl;
- blastAxisSystem.ConvertScene(scene);
- }
-
- FbxSystemUnit blastUnits = FbxUtils::getBlastFBXUnit();
- FbxSystemUnit sourceUnits = scene->GetGlobalSettings().GetSystemUnit();
- if (sourceUnits != blastUnits)
- {
- std::cout << "Converting to Blast units" << std::endl;
- std::cout << "Existing units: " << FbxUtils::FbxSystemUnitToString(sourceUnits) << std::endl;
- blastUnits.ConvertScene(scene);
- }
-
- FbxGeometryConverter geoConverter(sdkManager.get());
- FbxDisplayLayer* collisionDisplayLayer = scene->FindMember<FbxDisplayLayer>(FbxUtils::getCollisionGeometryLayerName().c_str());
-
- // Recurse the fbx tree and find all meshes
- std::vector<FbxNode*> meshNodes;
- getFbxMeshes(collisionDisplayLayer, scene->GetRootNode(), meshNodes);
-
- if (isCollisionLoaded())
- {
- std::cout << "Collision geometry is found.";
- getCollisionInternal();
- }
-
- std::cout << "Found " << meshNodes.size() << " meshes." << std::endl;
-
- if (meshNodes.size() > 1)
- {
- FbxArray<FbxNode*> tempMeshArray;
- tempMeshArray.Resize((int)meshNodes.size());
- for (size_t i = 0; i < meshNodes.size(); i++)
- {
- FbxMesh* mesh = meshNodes[i]->GetMesh();
- if (mesh->GetDeformerCount(FbxDeformer::eSkin) != 0)
- {
- std::cerr << "Multi-part mesh " << meshNodes[i]->GetName() << " is already skinned, not sure what to do" << std::endl;
- return;
- }
- //Add a one-bone skin so later when we merge meshes the connection to the chunk transform will stick, this handles the non-skinned layout of the FBX file
- FbxSkin* skin = FbxSkin::Create(scene, (std::string(meshNodes[i]->GetName()) + "_skin").c_str());
- mesh->AddDeformer(skin);
- FbxCluster* cluster = FbxCluster::Create(skin, (std::string(meshNodes[i]->GetName()) + "_cluster").c_str());
- skin->AddCluster(cluster);
- cluster->SetLink(meshNodes[i]);
- const int cpCount = mesh->GetControlPointsCount();
- cluster->SetControlPointIWCount(cpCount);
- //Fully weight to the one bone
- int* cpIdx = cluster->GetControlPointIndices();
- double* cpWeights = cluster->GetControlPointWeights();
- for (int cp = 0; cp < cpCount; cp++)
- {
- cpIdx[cp] = cp;
- cpWeights[cp] = 1.0;
- }
- tempMeshArray.SetAt(int(i), meshNodes[i]);
- }
- meshNodes.resize(1);
- meshNodes[0] = geoConverter.MergeMeshes(tempMeshArray, "MergedMesh", scene);
- }
-
- if (meshNodes.empty())
- {
- return;
- }
-
- FbxNode* meshNode = meshNodes[0];
- FbxMesh* mesh = meshNode->GetMesh();
-
- // Verify that the mesh is triangulated.
- bool bAllTriangles = mesh->IsTriangleMesh();
- if (!bAllTriangles)
- {
- //try letting the FBX SDK triangulate it
- geoConverter.Triangulate(mesh, true);
- bAllTriangles = mesh->IsTriangleMesh();
- }
-
- int polyCount = mesh->GetPolygonCount();
- if (!bAllTriangles)
- {
- std::cerr << "Mesh 0 has " << polyCount << " but not all polygons are triangles. Mesh must be triangulated." << std::endl;
- return;
- }
-
- 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);
- FbxAMatrix normalTransf = trans.Inverse().Transpose();
-
- int32_t matElements = mesh->GetElementMaterialCount();
- if (matElements > 1)
- {
- std::cerr << "Mesh has more than 1 material mappings, first one will be used. " << std::endl;
- }
- auto matLayer = mesh->GetElementMaterial(0);
- auto smLayer = mesh->GetElementSmoothing();
-
- const int triangleIndexMappingUnflipped[3] = { 0, 1, 2 };
- const int triangleIndexMappingFlipped[3] = { 2, 1, 0 };
- const int* triangleIndexMapping = trans.Determinant() < 0 ? triangleIndexMappingFlipped : triangleIndexMappingUnflipped;
-
- for (int i = 0; i < polyCount; i++)
- {
- for (int vi = 0; vi < 3; vi++)
- {
- int polyCPIdx = polyVertices[i*3+ triangleIndexMapping[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 = normalTransf.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++);
- }
- if (matLayer != nullptr)
- {
- mMaterialIds.push_back(matLayer->GetIndexArray().GetAt(i));
- }
- if (smLayer != nullptr)
- {
- mSmoothingGroups.push_back(smLayer->GetDirectArray().GetAt(i));
- }
- }
-
- mVertexPositions = positions;
- mVertexNormals = normals;
- mVertexUv = uv;
- mIndices = indices;
-
- getBoneInfluencesInternal(mesh);
-}
-
-int32_t* FbxFileReader::getSmoothingGroups()
-{
- if (!mSmoothingGroups.empty())
- {
- return mSmoothingGroups.data();
- }
- else
- {
- return nullptr;
- }
-}
-
-int32_t FbxFileReader::getMaterialCount()
-{
- return mMaterialNames.size();
-}
-
-void FbxFileReader::getFbxMeshes(FbxDisplayLayer* collisionDisplayLayer, FbxNode* node, std::vector<FbxNode*>& meshNodes)
-{
- FbxMesh* mesh = node->GetMesh();
-
- if (mesh != nullptr)
- {
- if (collisionDisplayLayer == nullptr && node->FindProperty("ParentalChunkIndex").IsValid())
- {
- //Old-style file
- uint32_t chunkIndex = node->FindProperty("ParentalChunkIndex").Get<int32_t>();
- mCollisionNodes.emplace(chunkIndex, node);
- }
- else if (collisionDisplayLayer != nullptr && collisionDisplayLayer->IsMember(node))
- {
- uint32_t chunkIndex = FbxUtils::getChunkIndexForNode(node);
- if (chunkIndex != UINT32_MAX)
- {
- mCollisionNodes.emplace(chunkIndex, node);
- }
- else
- {
- std::cerr << "Warning: Not sure what to do about collision geo " << node->GetName() << ". No corresponding chunk." << std::endl;
- }
- }
- else
- {
- meshNodes.push_back(node);
- }
- }
- int childCount = node->GetChildCount();
-
- for (int i = 0; i < childCount; i++)
- {
- FbxNode * childNode = node->GetChild(i);
-
- getFbxMeshes(collisionDisplayLayer, childNode, meshNodes);
- }
-}
-
-bool FbxFileReader::isCollisionLoaded()
-{
- return !mCollisionNodes.empty();
-}
-
-uint32_t FbxFileReader::getCollision(uint32_t*& hullsOffset, Nv::Blast::CollisionHull**& hulls)
-{
- if (!isCollisionLoaded())
- {
- hullsOffset = nullptr;
- hulls = nullptr;
- return 0;
- }
- hullsOffset = (uint32_t*)NVBLAST_ALLOC(sizeof(uint32_t) * mHullsOffset.size());
- memcpy(hullsOffset, mHullsOffset.data(), sizeof(uint32_t) * mHullsOffset.size());
-
-
- hulls = (Nv::Blast::CollisionHull**)NVBLAST_ALLOC(sizeof(Nv::Blast::CollisionHull*) * mHulls.size());
- for (size_t i = 0; i < mHulls.size(); i++)
- {
- //This deep-copies the data inside
- hulls[i] = new CollisionHullImpl(mHulls[i]);
- }
- return mMeshCount;
-}
-
-bool FbxFileReader::getCollisionInternal()
-{
- mHulls.clear();
- int32_t maxParentIndex = 0;
-
- for (auto p : mCollisionNodes)
- {
- int32_t parentIndex = p.first;
- maxParentIndex = std::max(maxParentIndex, parentIndex);
- }
- mMeshCount = maxParentIndex + 1;
- mHullsOffset.resize(mMeshCount + 1);
- mHulls.resize(mCollisionNodes.size());
-
- uint32_t currentHullCount = 0;
- uint32_t prevParentIndex = 0;
- mHullsOffset[0] = currentHullCount;
-
- for (auto p : mCollisionNodes) // it should be sorted by chunk id
- {
- uint32_t parentIndex = p.first;
- if (prevParentIndex != parentIndex)
- {
- for (uint32_t m = prevParentIndex + 1; m < parentIndex; m++)
- {
- //copy these if there were no collision meshes
- mHullsOffset[m] = mHullsOffset[prevParentIndex];
- }
- mHullsOffset[parentIndex] = currentHullCount;
- prevParentIndex = parentIndex;
- }
- Nv::Blast::CollisionHull& chull = mHulls[currentHullCount];
- currentHullCount++;
-
- FbxMesh* meshNode = p.second->GetMesh();
-
- FbxAMatrix nodeTransform = getTransformForNode(p.second);
- FbxAMatrix nodeTransformNormal = nodeTransform.Inverse().Transpose();
-
- //PhysX seems to care about having welding verticies.
- //Probably doing a dumb search is fast enough since how big could the convex hulls possibly be?
- std::vector<FbxVector4> uniqueCPValues;
- uniqueCPValues.reserve(meshNode->GetControlPointsCount());
- std::vector<uint32_t> originalToNewCPMapping(meshNode->GetControlPointsCount(), ~0U);
-
- FbxVector4* vpos = meshNode->GetControlPoints();
- for (int32_t i = 0; i < meshNode->GetControlPointsCount(); ++i)
- {
- FbxVector4 worldVPos = nodeTransform.MultT(*vpos);
- bool found = false;
- for (size_t j = 0; j < uniqueCPValues.size(); j++)
- {
- if (uniqueCPValues[j] == worldVPos)
- {
- originalToNewCPMapping[i] = uint32_t(j);
- found = true;
- break;
- }
- }
- if (!found)
- {
- originalToNewCPMapping[i] = uint32_t(uniqueCPValues.size());
- uniqueCPValues.push_back(worldVPos);
- }
- vpos++;
- }
-
- chull.points = new PxVec3[uniqueCPValues.size()];
- chull.pointsCount = uint32_t(uniqueCPValues.size());
-
- physx::PxVec3 hullCentroid(0.0f);
-
- for (uint32_t i = 0; i < chull.pointsCount; ++i)
- {
- const FbxVector4& worldVPos = uniqueCPValues[i];
- chull.points[i].x = (float)worldVPos[0];
- chull.points[i].y = (float)worldVPos[1];
- chull.points[i].z = (float)worldVPos[2];
- hullCentroid += chull.points[i];
- }
-
- if (chull.pointsCount)
- {
- hullCentroid /= (float)chull.pointsCount;
- }
-
- uint32_t polyCount = meshNode->GetPolygonCount();
- chull.polygonData = new Nv::Blast::CollisionHull::HullPolygon[polyCount];
- chull.polygonDataCount = polyCount;
-
- chull.indicesCount = meshNode->GetPolygonVertexCount();
- chull.indices = new uint32_t[chull.indicesCount];
- uint32_t curIndexCount = 0;
-
- for (uint32_t poly = 0; poly < polyCount; ++poly)
- {
- int32_t vInPolyCount = meshNode->GetPolygonSize(poly);
- auto& pd = chull.polygonData[poly];
- pd.mIndexBase = (uint16_t)curIndexCount;
- pd.mNbVerts = (uint16_t)vInPolyCount;
- int32_t* ind = &meshNode->GetPolygonVertices()[meshNode->GetPolygonVertexIndex(poly)];
- uint32_t* destInd = chull.indices + curIndexCount;
- for (int32_t v = 0; v < vInPolyCount; v++)
- {
- destInd[v] = originalToNewCPMapping[ind[v]];
- }
- curIndexCount += vInPolyCount;
-
- //Don't depend on the normals to create the plane normal, they could be wrong
- PxVec3 lastThreeVerts[3] = {
- chull.points[chull.indices[curIndexCount - 1]],
- chull.points[chull.indices[curIndexCount - 2]],
- chull.points[chull.indices[curIndexCount - 3]]
- };
-
- physx::PxPlane plane(lastThreeVerts[0], lastThreeVerts[1], lastThreeVerts[2]);
- plane.normalize();
-
- const float s = plane.n.dot(lastThreeVerts[0] - hullCentroid) >= 0.0f ? 1.0f : -1.0f;
-
- pd.mPlane[0] = s*plane.n.x;
- pd.mPlane[1] = s*plane.n.y;
- pd.mPlane[2] = s*plane.n.z;
- pd.mPlane[3] = s*plane.d;
- }
- }
-
- //Set the end marker
- for (uint32_t m = prevParentIndex + 1; m <= mMeshCount; m++)
- {
- //copy these if there were no collision meshes
- mHullsOffset[m] = currentHullCount;
- }
-
- return false;
-}
-
-
-/**
- To work properly export tool should give bone names as bone_@chunkIndex (e.g. bone_1, bone_2)
-**/
-bool FbxFileReader::getBoneInfluencesInternal(FbxMesh* meshNode)
-{
- std::unordered_map<FbxNode*, uint32_t> boneToChunkIndex;
-
- if (meshNode->GetDeformerCount() != 1)
- {
- std::cout << "Can't create bone mapping: There is no mesh deformers...: " << std::endl;
- return false;
- }
- mVertexToContainingChunkMap.clear();
- mVertexToContainingChunkMap.resize(mVertexPositions.size());
- std::vector<uint32_t> controlToParentChunkMap;
- controlToParentChunkMap.resize(meshNode->GetControlPointsCount());
- FbxSkin* def = (FbxSkin *)meshNode->GetDeformer(0, FbxDeformer::EDeformerType::eSkin);
-
- if (def->GetClusterCount() == 0)
- {
- std::cout << "Can't create bone mapping: There is no vertex clusters...: " << std::endl;
- return false;
- }
- //We want the number of chunks not the bones in the FBX file
- mChunkCount = 0;
-
- for (int32_t i = 0; i < def->GetClusterCount(); ++i)
- {
- FbxCluster* cls = def->GetCluster(i);
- FbxNode* bone = cls->GetLink();
-
- uint32_t myChunkIndex;
- auto findIt = boneToChunkIndex.find(bone);
- if (findIt != boneToChunkIndex.end())
- {
- myChunkIndex = findIt->second;
- }
- else
- {
- myChunkIndex = FbxUtils::getChunkIndexForNode(bone);
- if (myChunkIndex == UINT32_MAX)
- {
- //maybe an old file?
- myChunkIndex = FbxUtils::getChunkIndexForNodeBackwardsCompatible(bone);
- }
-
- if (myChunkIndex == UINT32_MAX)
- {
- std::cerr << "Not sure what to do with node " << bone->GetName() << ". is this a chunk?" << std::endl;
- }
-
- boneToChunkIndex.emplace(bone, myChunkIndex);
- if (myChunkIndex >= mChunkCount)
- {
- mChunkCount = myChunkIndex + 1;
- }
- }
-
- int32_t* cpIndx = cls->GetControlPointIndices();
- for (int32_t j = 0; j < cls->GetControlPointIndicesCount(); ++j)
- {
- controlToParentChunkMap[*cpIndx] = myChunkIndex;
- ++cpIndx;
- }
- }
-
- int* polyVertices = meshNode->GetPolygonVertices();
- uint32_t lv = 0;
- for (int i = 0; i < meshNode->GetPolygonCount(); i++)
- {
- for (int vi = 0; vi < 3; vi++)
- {
- mVertexToContainingChunkMap[lv] = controlToParentChunkMap[*polyVertices];
- polyVertices++;
- lv++;
- }
- }
- return true;
-};
-
-physx::PxVec3* FbxFileReader::getPositionArray()
-{
- return mVertexPositions.data();
-};
-
-physx::PxVec3* FbxFileReader::getNormalsArray()
-{
- return mVertexNormals.data();
-};
-
-physx::PxVec2* FbxFileReader::getUvArray()
-{
- return mVertexUv.data();
-};
-
-uint32_t* FbxFileReader::getIndexArray()
-{
- return mIndices.data();
-};
-
-uint32_t FbxFileReader::getBoneInfluences(uint32_t*& out)
-{
- out = static_cast<uint32_t*>(NVBLAST_ALLOC(sizeof(uint32_t) * mVertexToContainingChunkMap.size()));
- memcpy(out, mVertexToContainingChunkMap.data(), sizeof(uint32_t) * mVertexToContainingChunkMap.size());
- return mVertexToContainingChunkMap.size();
-}
-
-uint32_t FbxFileReader::getBoneCount()
-{
- return mChunkCount;
-}
-
-const char* FbxFileReader::getMaterialName(int32_t id)
-{
- if (id < int32_t(mMaterialNames.size()) && id >= 0)
- {
- return mMaterialNames[id].c_str();
- }
- else
- {
- return nullptr;
- }
-}
-
-int32_t* FbxFileReader::getMaterialIds()
-{
- if (mMaterialIds.empty())
- {
- return nullptr;
- }
- return mMaterialIds.data();
-}
+// This code contains NVIDIA Confidential Information and is disclosed to you
+// under a form of NVIDIA software license agreement provided separately to you.
+//
+// Notice
+// NVIDIA Corporation and its licensors retain all intellectual property and
+// proprietary rights in and to this software and related documentation and
+// any modifications thereto. Any use, reproduction, disclosure, or
+// distribution of this software and related documentation without an express
+// license agreement from NVIDIA Corporation is strictly prohibited.
+//
+// ALL NVIDIA DESIGN SPECIFICATIONS, CODE ARE PROVIDED "AS IS.". NVIDIA MAKES
+// NO WARRANTIES, EXPRESSED, IMPLIED, STATUTORY, OR OTHERWISE WITH RESPECT TO
+// THE MATERIALS, AND EXPRESSLY DISCLAIMS ALL IMPLIED WARRANTIES OF NONINFRINGEMENT,
+// MERCHANTABILITY, AND FITNESS FOR A PARTICULAR PURPOSE.
+//
+// Information and code furnished is believed to be accurate and reliable.
+// However, NVIDIA Corporation assumes no responsibility for the consequences of use of such
+// information or for any infringement of patents or other rights of third parties that may
+// result from its use. No license is granted by implication or otherwise under any patent
+// or patent rights of NVIDIA Corporation. Details are subject to change without notice.
+// This code supersedes and replaces all information previously supplied.
+// NVIDIA Corporation products are not authorized for use as critical
+// components in life support devices or systems without express written approval of
+// NVIDIA Corporation.
+//
+// Copyright (c) 2018 NVIDIA Corporation. All rights reserved.
+
+#include "NvBlastExtExporterFbxReader.h"
+#include "NvBlastExtExporterFbxUtils.h"
+#include "NvBlastGlobals.h"
+#include <fbxsdk.h>
+#include <iostream>
+#include <algorithm>
+#include <cctype>
+#include <sstream>
+#include <unordered_map>
+
+#include "PxVec3.h"
+#include "PxVec2.h"
+#include "PxPlane.h"
+#include "NvBlastExtAuthoringMesh.h"
+#include "NvBlastExtAuthoringBondGenerator.h"
+#include "NvBlastExtAuthoringCollisionBuilder.h"
+
+using physx::PxVec3;
+using physx::PxVec2;
+
+using namespace Nv::Blast;
+
+FbxFileReader::FbxFileReader()
+{
+ mMeshCount = 0;
+ mChunkCount = 0;
+}
+
+void FbxFileReader::release()
+{
+ delete this;
+}
+
+FbxAMatrix FbxFileReader::getTransformForNode(FbxNode* node)
+{
+ //The geometry transform contains the information about the pivots in the mesh node relative to the node's transform
+ FbxAMatrix geometryTransform(node->GetGeometricTranslation(FbxNode::eSourcePivot),
+ node->GetGeometricRotation(FbxNode::eSourcePivot),
+ node->GetGeometricScaling(FbxNode::eSourcePivot));
+ FbxAMatrix nodeTransform = node->EvaluateGlobalTransform();
+
+ return nodeTransform * geometryTransform;
+}
+
+void FbxFileReader::loadFromFile(const char* 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)
+ {
+ manager->Destroy();
+ });
+
+ mChunkCount = 0;
+ mCollisionNodes.clear();
+ 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, -1, sdkManager->GetIOSettings());
+
+ if (!importStatus)
+ {
+ std::cerr << "Call to FbxImporter::Initialize failed." << std::endl;
+ std::cerr << "Error returned: " << importer->GetStatus().GetErrorString() << std::endl;
+
+ return;
+ }
+
+ 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;
+ }
+
+ int32_t matCount = scene->GetMaterialCount();
+
+ for (int32_t i = 0; i < matCount; ++i)
+ {
+ mMaterialNames.push_back(std::string(scene->GetMaterial(i)->GetName()));
+ }
+
+ //This removes axis and unit conversion nodes so it converts the entire scene to the header specified axis and units
+ FbxRootNodeUtility::RemoveAllFbxRoots(scene);
+
+ FbxAxisSystem blastAxisSystem = FbxUtils::getBlastFBXAxisSystem();
+ FbxAxisSystem sourceSetup = scene->GetGlobalSettings().GetAxisSystem();
+ if (sourceSetup != blastAxisSystem)
+ {
+ std::cout << "Converting to Blast coordinates" << std::endl;
+ std::cout << "Existing axis: " << FbxUtils::FbxAxisSystemToString(sourceSetup) << std::endl;
+ blastAxisSystem.ConvertScene(scene);
+ }
+
+ FbxSystemUnit blastUnits = FbxUtils::getBlastFBXUnit();
+ FbxSystemUnit sourceUnits = scene->GetGlobalSettings().GetSystemUnit();
+ if (sourceUnits != blastUnits)
+ {
+ std::cout << "Converting to Blast units" << std::endl;
+ std::cout << "Existing units: " << FbxUtils::FbxSystemUnitToString(sourceUnits) << std::endl;
+ blastUnits.ConvertScene(scene);
+ }
+
+ FbxGeometryConverter geoConverter(sdkManager.get());
+ FbxDisplayLayer* collisionDisplayLayer = scene->FindMember<FbxDisplayLayer>(FbxUtils::getCollisionGeometryLayerName().c_str());
+
+ // Recurse the fbx tree and find all meshes
+ std::vector<FbxNode*> meshNodes;
+ getFbxMeshes(collisionDisplayLayer, scene->GetRootNode(), meshNodes);
+
+ if (isCollisionLoaded())
+ {
+ std::cout << "Collision geometry is found.";
+ getCollisionInternal();
+ }
+
+ std::cout << "Found " << meshNodes.size() << " meshes." << std::endl;
+
+ if (meshNodes.size() > 1)
+ {
+ FbxArray<FbxNode*> tempMeshArray;
+ tempMeshArray.Resize((int)meshNodes.size());
+ for (size_t i = 0; i < meshNodes.size(); i++)
+ {
+ FbxMesh* mesh = meshNodes[i]->GetMesh();
+ if (mesh->GetDeformerCount(FbxDeformer::eSkin) != 0)
+ {
+ std::cerr << "Multi-part mesh " << meshNodes[i]->GetName() << " is already skinned, not sure what to do" << std::endl;
+ return;
+ }
+ //Add a one-bone skin so later when we merge meshes the connection to the chunk transform will stick, this handles the non-skinned layout of the FBX file
+ FbxSkin* skin = FbxSkin::Create(scene, (std::string(meshNodes[i]->GetName()) + "_skin").c_str());
+ mesh->AddDeformer(skin);
+ FbxCluster* cluster = FbxCluster::Create(skin, (std::string(meshNodes[i]->GetName()) + "_cluster").c_str());
+ skin->AddCluster(cluster);
+ cluster->SetLink(meshNodes[i]);
+ const int cpCount = mesh->GetControlPointsCount();
+ cluster->SetControlPointIWCount(cpCount);
+ //Fully weight to the one bone
+ int* cpIdx = cluster->GetControlPointIndices();
+ double* cpWeights = cluster->GetControlPointWeights();
+ for (int cp = 0; cp < cpCount; cp++)
+ {
+ cpIdx[cp] = cp;
+ cpWeights[cp] = 1.0;
+ }
+ tempMeshArray.SetAt(int(i), meshNodes[i]);
+ }
+ meshNodes.resize(1);
+ meshNodes[0] = geoConverter.MergeMeshes(tempMeshArray, "MergedMesh", scene);
+ }
+
+ if (meshNodes.empty())
+ {
+ return;
+ }
+
+ FbxNode* meshNode = meshNodes[0];
+ FbxMesh* mesh = meshNode->GetMesh();
+
+ // Verify that the mesh is triangulated.
+ bool bAllTriangles = mesh->IsTriangleMesh();
+ if (!bAllTriangles)
+ {
+ //try letting the FBX SDK triangulate it
+ geoConverter.Triangulate(mesh, true);
+ bAllTriangles = mesh->IsTriangleMesh();
+ }
+
+ int polyCount = mesh->GetPolygonCount();
+ if (!bAllTriangles)
+ {
+ std::cerr << "Mesh 0 has " << polyCount << " but not all polygons are triangles. Mesh must be triangulated." << std::endl;
+ return;
+ }
+
+ 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);
+ FbxAMatrix normalTransf = trans.Inverse().Transpose();
+
+ int32_t matElements = mesh->GetElementMaterialCount();
+ if (matElements > 1)
+ {
+ std::cerr << "Mesh has more than 1 material mappings, first one will be used. " << std::endl;
+ }
+ auto matLayer = mesh->GetElementMaterial(0);
+ auto smLayer = mesh->GetElementSmoothing();
+
+ const int triangleIndexMappingUnflipped[3] = { 0, 1, 2 };
+ const int triangleIndexMappingFlipped[3] = { 2, 1, 0 };
+ const int* triangleIndexMapping = trans.Determinant() < 0 ? triangleIndexMappingFlipped : triangleIndexMappingUnflipped;
+
+ for (int i = 0; i < polyCount; i++)
+ {
+ for (int vi = 0; vi < 3; vi++)
+ {
+ int polyCPIdx = polyVertices[i*3+ triangleIndexMapping[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 = normalTransf.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++);
+ }
+ if (matLayer != nullptr)
+ {
+ mMaterialIds.push_back(matLayer->GetIndexArray().GetAt(i));
+ }
+ if (smLayer != nullptr)
+ {
+ mSmoothingGroups.push_back(smLayer->GetDirectArray().GetAt(i));
+ }
+ }
+
+ mVertexPositions = positions;
+ mVertexNormals = normals;
+ mVertexUv = uv;
+ mIndices = indices;
+
+ getBoneInfluencesInternal(mesh);
+}
+
+int32_t* FbxFileReader::getSmoothingGroups()
+{
+ if (!mSmoothingGroups.empty())
+ {
+ return mSmoothingGroups.data();
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+int32_t FbxFileReader::getMaterialCount()
+{
+ return mMaterialNames.size();
+}
+
+void FbxFileReader::getFbxMeshes(FbxDisplayLayer* collisionDisplayLayer, FbxNode* node, std::vector<FbxNode*>& meshNodes)
+{
+ FbxMesh* mesh = node->GetMesh();
+
+ if (mesh != nullptr)
+ {
+ if (collisionDisplayLayer == nullptr && node->FindProperty("ParentalChunkIndex").IsValid())
+ {
+ //Old-style file
+ uint32_t chunkIndex = node->FindProperty("ParentalChunkIndex").Get<int32_t>();
+ mCollisionNodes.emplace(chunkIndex, node);
+ }
+ else if (collisionDisplayLayer != nullptr && collisionDisplayLayer->IsMember(node))
+ {
+ uint32_t chunkIndex = FbxUtils::getChunkIndexForNode(node);
+ if (chunkIndex != UINT32_MAX)
+ {
+ mCollisionNodes.emplace(chunkIndex, node);
+ }
+ else
+ {
+ std::cerr << "Warning: Not sure what to do about collision geo " << node->GetName() << ". No corresponding chunk." << std::endl;
+ }
+ }
+ else
+ {
+ meshNodes.push_back(node);
+ }
+ }
+ int childCount = node->GetChildCount();
+
+ for (int i = 0; i < childCount; i++)
+ {
+ FbxNode * childNode = node->GetChild(i);
+
+ getFbxMeshes(collisionDisplayLayer, childNode, meshNodes);
+ }
+}
+
+bool FbxFileReader::isCollisionLoaded()
+{
+ return !mCollisionNodes.empty();
+}
+
+uint32_t FbxFileReader::getCollision(uint32_t*& hullsOffset, Nv::Blast::CollisionHull**& hulls)
+{
+ if (!isCollisionLoaded())
+ {
+ hullsOffset = nullptr;
+ hulls = nullptr;
+ return 0;
+ }
+ hullsOffset = (uint32_t*)NVBLAST_ALLOC(sizeof(uint32_t) * mHullsOffset.size());
+ memcpy(hullsOffset, mHullsOffset.data(), sizeof(uint32_t) * mHullsOffset.size());
+
+
+ hulls = (Nv::Blast::CollisionHull**)NVBLAST_ALLOC(sizeof(Nv::Blast::CollisionHull*) * mHulls.size());
+ for (size_t i = 0; i < mHulls.size(); i++)
+ {
+ //This deep-copies the data inside
+ hulls[i] = new CollisionHullImpl(mHulls[i]);
+ }
+ return mMeshCount;
+}
+
+bool FbxFileReader::getCollisionInternal()
+{
+ mHulls.clear();
+ int32_t maxParentIndex = 0;
+
+ for (auto p : mCollisionNodes)
+ {
+ int32_t parentIndex = p.first;
+ maxParentIndex = std::max(maxParentIndex, parentIndex);
+ }
+ mMeshCount = maxParentIndex + 1;
+ mHullsOffset.resize(mMeshCount + 1);
+ mHulls.resize(mCollisionNodes.size());
+
+ uint32_t currentHullCount = 0;
+ uint32_t prevParentIndex = 0;
+ mHullsOffset[0] = currentHullCount;
+
+ for (auto p : mCollisionNodes) // it should be sorted by chunk id
+ {
+ uint32_t parentIndex = p.first;
+ if (prevParentIndex != parentIndex)
+ {
+ for (uint32_t m = prevParentIndex + 1; m < parentIndex; m++)
+ {
+ //copy these if there were no collision meshes
+ mHullsOffset[m] = mHullsOffset[prevParentIndex];
+ }
+ mHullsOffset[parentIndex] = currentHullCount;
+ prevParentIndex = parentIndex;
+ }
+ Nv::Blast::CollisionHull& chull = mHulls[currentHullCount];
+ currentHullCount++;
+
+ FbxMesh* meshNode = p.second->GetMesh();
+
+ FbxAMatrix nodeTransform = getTransformForNode(p.second);
+ FbxAMatrix nodeTransformNormal = nodeTransform.Inverse().Transpose();
+
+ //PhysX seems to care about having welding verticies.
+ //Probably doing a dumb search is fast enough since how big could the convex hulls possibly be?
+ std::vector<FbxVector4> uniqueCPValues;
+ uniqueCPValues.reserve(meshNode->GetControlPointsCount());
+ std::vector<uint32_t> originalToNewCPMapping(meshNode->GetControlPointsCount(), ~0U);
+
+ FbxVector4* vpos = meshNode->GetControlPoints();
+ for (int32_t i = 0; i < meshNode->GetControlPointsCount(); ++i)
+ {
+ FbxVector4 worldVPos = nodeTransform.MultT(*vpos);
+ bool found = false;
+ for (size_t j = 0; j < uniqueCPValues.size(); j++)
+ {
+ if (uniqueCPValues[j] == worldVPos)
+ {
+ originalToNewCPMapping[i] = uint32_t(j);
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ originalToNewCPMapping[i] = uint32_t(uniqueCPValues.size());
+ uniqueCPValues.push_back(worldVPos);
+ }
+ vpos++;
+ }
+
+ chull.points = new PxVec3[uniqueCPValues.size()];
+ chull.pointsCount = uint32_t(uniqueCPValues.size());
+
+ physx::PxVec3 hullCentroid(0.0f);
+
+ for (uint32_t i = 0; i < chull.pointsCount; ++i)
+ {
+ const FbxVector4& worldVPos = uniqueCPValues[i];
+ chull.points[i].x = (float)worldVPos[0];
+ chull.points[i].y = (float)worldVPos[1];
+ chull.points[i].z = (float)worldVPos[2];
+ hullCentroid += chull.points[i];
+ }
+
+ if (chull.pointsCount)
+ {
+ hullCentroid /= (float)chull.pointsCount;
+ }
+
+ uint32_t polyCount = meshNode->GetPolygonCount();
+ chull.polygonData = new Nv::Blast::CollisionHull::HullPolygon[polyCount];
+ chull.polygonDataCount = polyCount;
+
+ chull.indicesCount = meshNode->GetPolygonVertexCount();
+ chull.indices = new uint32_t[chull.indicesCount];
+ uint32_t curIndexCount = 0;
+
+ for (uint32_t poly = 0; poly < polyCount; ++poly)
+ {
+ int32_t vInPolyCount = meshNode->GetPolygonSize(poly);
+ auto& pd = chull.polygonData[poly];
+ pd.mIndexBase = (uint16_t)curIndexCount;
+ pd.mNbVerts = (uint16_t)vInPolyCount;
+ int32_t* ind = &meshNode->GetPolygonVertices()[meshNode->GetPolygonVertexIndex(poly)];
+ uint32_t* destInd = chull.indices + curIndexCount;
+ for (int32_t v = 0; v < vInPolyCount; v++)
+ {
+ destInd[v] = originalToNewCPMapping[ind[v]];
+ }
+ curIndexCount += vInPolyCount;
+
+ //Don't depend on the normals to create the plane normal, they could be wrong
+ PxVec3 lastThreeVerts[3] = {
+ chull.points[chull.indices[curIndexCount - 1]],
+ chull.points[chull.indices[curIndexCount - 2]],
+ chull.points[chull.indices[curIndexCount - 3]]
+ };
+
+ physx::PxPlane plane(lastThreeVerts[0], lastThreeVerts[1], lastThreeVerts[2]);
+ plane.normalize();
+
+ const float s = plane.n.dot(lastThreeVerts[0] - hullCentroid) >= 0.0f ? 1.0f : -1.0f;
+
+ pd.mPlane[0] = s*plane.n.x;
+ pd.mPlane[1] = s*plane.n.y;
+ pd.mPlane[2] = s*plane.n.z;
+ pd.mPlane[3] = s*plane.d;
+ }
+ }
+
+ //Set the end marker
+ for (uint32_t m = prevParentIndex + 1; m <= mMeshCount; m++)
+ {
+ //copy these if there were no collision meshes
+ mHullsOffset[m] = currentHullCount;
+ }
+
+ return false;
+}
+
+
+/**
+ To work properly export tool should give bone names as bone_@chunkIndex (e.g. bone_1, bone_2)
+**/
+bool FbxFileReader::getBoneInfluencesInternal(FbxMesh* meshNode)
+{
+ std::unordered_map<FbxNode*, uint32_t> boneToChunkIndex;
+
+ if (meshNode->GetDeformerCount() != 1)
+ {
+ std::cout << "Can't create bone mapping: There is no mesh deformers...: " << std::endl;
+ return false;
+ }
+ mVertexToContainingChunkMap.clear();
+ mVertexToContainingChunkMap.resize(mVertexPositions.size());
+ std::vector<uint32_t> controlToParentChunkMap;
+ controlToParentChunkMap.resize(meshNode->GetControlPointsCount());
+ FbxSkin* def = (FbxSkin *)meshNode->GetDeformer(0, FbxDeformer::EDeformerType::eSkin);
+
+ if (def->GetClusterCount() == 0)
+ {
+ std::cout << "Can't create bone mapping: There is no vertex clusters...: " << std::endl;
+ return false;
+ }
+ //We want the number of chunks not the bones in the FBX file
+ mChunkCount = 0;
+
+ for (int32_t i = 0; i < def->GetClusterCount(); ++i)
+ {
+ FbxCluster* cls = def->GetCluster(i);
+ FbxNode* bone = cls->GetLink();
+
+ uint32_t myChunkIndex;
+ auto findIt = boneToChunkIndex.find(bone);
+ if (findIt != boneToChunkIndex.end())
+ {
+ myChunkIndex = findIt->second;
+ }
+ else
+ {
+ myChunkIndex = FbxUtils::getChunkIndexForNode(bone);
+ if (myChunkIndex == UINT32_MAX)
+ {
+ //maybe an old file?
+ myChunkIndex = FbxUtils::getChunkIndexForNodeBackwardsCompatible(bone);
+ }
+
+ if (myChunkIndex == UINT32_MAX)
+ {
+ std::cerr << "Not sure what to do with node " << bone->GetName() << ". is this a chunk?" << std::endl;
+ }
+
+ boneToChunkIndex.emplace(bone, myChunkIndex);
+ if (myChunkIndex >= mChunkCount)
+ {
+ mChunkCount = myChunkIndex + 1;
+ }
+ }
+
+ int32_t* cpIndx = cls->GetControlPointIndices();
+ for (int32_t j = 0; j < cls->GetControlPointIndicesCount(); ++j)
+ {
+ controlToParentChunkMap[*cpIndx] = myChunkIndex;
+ ++cpIndx;
+ }
+ }
+
+ int* polyVertices = meshNode->GetPolygonVertices();
+ uint32_t lv = 0;
+ for (int i = 0; i < meshNode->GetPolygonCount(); i++)
+ {
+ for (int vi = 0; vi < 3; vi++)
+ {
+ mVertexToContainingChunkMap[lv] = controlToParentChunkMap[*polyVertices];
+ polyVertices++;
+ lv++;
+ }
+ }
+ return true;
+};
+
+physx::PxVec3* FbxFileReader::getPositionArray()
+{
+ return mVertexPositions.data();
+};
+
+physx::PxVec3* FbxFileReader::getNormalsArray()
+{
+ return mVertexNormals.data();
+};
+
+physx::PxVec2* FbxFileReader::getUvArray()
+{
+ return mVertexUv.data();
+};
+
+uint32_t* FbxFileReader::getIndexArray()
+{
+ return mIndices.data();
+};
+
+uint32_t FbxFileReader::getBoneInfluences(uint32_t*& out)
+{
+ out = static_cast<uint32_t*>(NVBLAST_ALLOC(sizeof(uint32_t) * mVertexToContainingChunkMap.size()));
+ memcpy(out, mVertexToContainingChunkMap.data(), sizeof(uint32_t) * mVertexToContainingChunkMap.size());
+ return mVertexToContainingChunkMap.size();
+}
+
+uint32_t FbxFileReader::getBoneCount()
+{
+ return mChunkCount;
+}
+
+const char* FbxFileReader::getMaterialName(int32_t id)
+{
+ if (id < int32_t(mMaterialNames.size()) && id >= 0)
+ {
+ return mMaterialNames[id].c_str();
+ }
+ else
+ {
+ return nullptr;
+ }
+}
+
+int32_t* FbxFileReader::getMaterialIds()
+{
+ if (mMaterialIds.empty())
+ {
+ return nullptr;
+ }
+ return mMaterialIds.data();
+}