Browse Source

Refactored and improved FBX importer

Marko Pintera 10 years ago
parent
commit
a643070792

+ 6 - 6
BansheeEditor/Source/BsEditorApplication.cpp

@@ -184,14 +184,14 @@ namespace BansheeEngine
 
 		mTestMaterial = Material::create(mTestShader);
 
-		//mTestTexRef = static_resource_cast<Texture>(Importer::instance().import(L"..\\..\\..\\..\\Data\\Examples\\Dragon.tga"));
-		//mDbgMeshRef = static_resource_cast<Mesh>(Importer::instance().import(L"..\\..\\..\\..\\Data\\Examples\\Dragon.fbx"));
+		mTestTexRef = static_resource_cast<Texture>(Importer::instance().import(L"..\\..\\..\\..\\Data\\Examples\\Dragon.tga"));
+		mDbgMeshRef = static_resource_cast<Mesh>(Importer::instance().import(L"..\\..\\..\\..\\Data\\Examples\\Dragon.fbx"));
 
-		//gResources().save(mTestTexRef, L"C:\\ExportTest.tex", true);
-		//gResources().save(mDbgMeshRef, L"C:\\ExportMesh.mesh", true);
+		gResources().save(mTestTexRef, L"C:\\ExportTest.tex", true);
+		gResources().save(mDbgMeshRef, L"C:\\ExportMesh.mesh", true);
 
-		//gResources().unload(mTestTexRef);
-		//gResources().unload(mDbgMeshRef);
+		gResources().unload(mTestTexRef);
+		gResources().unload(mDbgMeshRef);
 
 		mTestTexRef = static_resource_cast<Texture>(gResources().loadAsync(L"C:\\ExportTest.tex"));
 		mDbgMeshRef = static_resource_cast<Mesh>(gResources().loadAsync(L"C:\\ExportMesh.mesh"));

+ 4 - 0
BansheeFBXImporter/BansheeFBXImporter.vcxproj

@@ -239,12 +239,16 @@
     </Link>
   </ItemDefinitionGroup>
   <ItemGroup>
+    <ClInclude Include="Include\BsFBXImportData.h" />
     <ClInclude Include="Include\BsFBXImporter.h" />
     <ClInclude Include="Include\BsFBXPrerequisites.h" />
+    <ClInclude Include="Include\BsFBXUtility.h" />
   </ItemGroup>
   <ItemGroup>
+    <ClCompile Include="Source\BsFBXImportdata.cpp" />
     <ClCompile Include="Source\BsFBXImporter.cpp" />
     <ClCompile Include="Source\BsFBXPlugin.cpp" />
+    <ClCompile Include="Source\BsFBXUtility.cpp" />
   </ItemGroup>
   <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   <ImportGroup Label="ExtensionTargets">

+ 12 - 0
BansheeFBXImporter/BansheeFBXImporter.vcxproj.filters

@@ -21,6 +21,12 @@
     <ClInclude Include="Include\BsFBXImporter.h">
       <Filter>Header Files</Filter>
     </ClInclude>
+    <ClInclude Include="Include\BsFBXImportData.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
+    <ClInclude Include="Include\BsFBXUtility.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\BsFBXPlugin.cpp">
@@ -29,5 +35,11 @@
     <ClCompile Include="Source\BsFBXImporter.cpp">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="Source\BsFBXImportdata.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
+    <ClCompile Include="Source\BsFBXUtility.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
 </Project>

+ 76 - 0
BansheeFBXImporter/Include/BsFBXImportData.h

@@ -0,0 +1,76 @@
+#pragma once
+
+#include "BsFBXPrerequisites.h"
+#include "BsMatrix4.h"
+#include "BsColor.h"
+#include "BsVector2.h"
+#include "BsVector4.h"
+#include "BsSubMesh.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Options that control FBX import
+	 */
+	struct FBXImportOptions
+	{
+		bool importAnimation = true;
+		bool importSkin = true;
+		bool importBlendShapes = true;
+		bool importNormals = true;
+		bool importTangents = true;
+	};
+
+	/**
+	 * @brief	Represents a single node in the FBX transform hierarchy.
+	 */
+	struct FBXImportNode
+	{
+		~FBXImportNode();
+
+		Matrix4 localTransform;
+		Matrix4 worldTransform;
+
+		Vector<FBXImportNode*> children;
+	};
+
+	/**
+	 * @brief	Imported mesh data
+	 */
+	struct FBXImportMesh
+	{
+		Vector<int> indices;
+		Vector<Vector4> positions;
+		Vector<Vector4> normals;
+		Vector<Vector4> tangents;
+		Vector<Vector4> bitangents;
+		Vector<RGBA> colors;
+		Vector<Vector2> UV[FBX_IMPORT_MAX_UV_LAYERS];
+		Vector<int> materials;
+
+		MeshDataPtr meshData;
+		Vector<SubMesh> subMeshes;
+
+		Vector<FBXImportNode*> referencedBy;
+
+		// TODO - Store blend shapes
+	};
+
+	/**
+	 * @brief	Scene information used and modified during FBX import.
+	 */
+	struct FBXImportScene
+	{
+		FBXImportScene();
+		~FBXImportScene();
+
+		Vector<FBXImportMesh*> meshes;
+		FBXImportNode* rootNode;
+
+		UnorderedMap<FbxNode*, FBXImportNode*> nodeMap;
+		UnorderedMap<FbxMesh*, UINT32> meshMap;
+
+		// TODO - Store animation & bones
+	};
+
+}

+ 25 - 11
BansheeFBXImporter/Include/BsFBXImporter.h

@@ -2,11 +2,10 @@
 
 #include "BsFBXPrerequisites.h"
 #include "BsSpecificImporter.h"
-#include "BsImporter.h"
 #include "BsSubMesh.h"
+#include "BsFBXImportData.h"
 
-#define FBXSDK_NEW_API
-#include <fbxsdk.h>
+#define FBX_IMPORT_MAX_UV_LAYERS 2
 
 namespace BansheeEngine
 {
@@ -40,39 +39,54 @@ namespace BansheeEngine
 		 *			Outputs an FBX manager and FBX scene instances you should use in
 		 *			further operations.
 		 */
-		void startUpSdk(FbxManager*& manager, FbxScene*& scene);
+		bool startUpSdk(FbxScene*& scene);
 
 		/**
 		 * @brief	Shuts down FBX SDK. Must be called after any other operations.
 		 */
-		void shutDownSdk(FbxManager* manager);
+		void shutDownSdk();
 
 		/**
 		 * @brief	Loads the data from the file at the provided path into the provided FBX scene. 
 		 *			Throws exception if data cannot be loaded.
 		 */
-		void loadScene(FbxManager* manager, FbxScene* scene, const Path& filePath);
+		bool loadFBXFile(FbxScene* scene, const Path& filePath);
 
 		/**
 		 * @brief	Parses an FBX scene. Find all meshes in the scene and returns mesh data object
 		 *			containing all vertices, indexes and other mesh information. Also outputs
 		 *			a sub-mesh array that allows you locate specific sub-meshes within the returned
-		 *			mesh data object.
+		 *			mesh data object. If requested animation and blend shape data is output as well.
 		 */
-		MeshDataPtr parseScene(FbxManager* manager, FbxScene* scene, Vector<SubMesh>& subMeshes);
+		void parseScene(FbxScene* scene, const FBXImportOptions& options, FBXImportScene& outputScene);
 
 		/**
 		 * @brief	Parses an FBX mesh. Converts it from FBX SDK format into a mesh data object containing
 		 *			one or multiple sub-meshes.
 		 */
-		MeshDataPtr parseMesh(FbxMesh* mesh, Vector<SubMesh>& subMeshes, bool createTangentsIfMissing = true);
+		void parseMesh(FbxMesh* mesh, FBXImportNode* parentNode, const FBXImportOptions& options, FBXImportScene& outputScene);
 
 		/**
-		 * @brief	Computes world transform matrix for the specified FBX node.
+		 * @brief	Converts all the meshes from per-index attributes to per-vertex attributes.
+		 *
+		 * @note	This method will replace all meshes in the scene with new ones, and delete old ones
+		 *			so be sure not to keep any mesh references.
 		 */
-		FbxAMatrix computeWorldTransform(FbxNode* node);
+		void splitMeshVertices(FBXImportScene& scene);
+
+		/**
+		 * @brief	Converts the mesh data from the imported FBX scene into mesh data that can be used
+		 *			for initializing a mesh.
+		 */
+		MeshDataPtr generateMeshData(const FBXImportScene& scene, Vector<SubMesh>& subMeshes);
+
+		/**
+		 * @brief	Creates an internal representation of an FBX node from an FbxNode object.
+		 */
+		FBXImportNode* createImportNode(FBXImportScene& scene, FbxNode* fbxNode, FBXImportNode* parent);
 
 	private:
 		Vector<WString> mExtensions;
+		FbxManager* mFBXManager;
 	};
 }

+ 6 - 1
BansheeFBXImporter/Include/BsFBXPrerequisites.h

@@ -16,4 +16,9 @@
 #    define BS_FBX_EXPORT  __attribute__ ((visibility("default")))
 #else
 #    define BS_FBX_EXPORT
-#endif
+#endif
+
+#define FBXSDK_NEW_API
+#include <fbxsdk.h>
+
+#define FBX_IMPORT_MAX_UV_LAYERS 2

+ 56 - 0
BansheeFBXImporter/Include/BsFBXUtility.h

@@ -0,0 +1,56 @@
+#pragma once
+
+#include "BsFBXPrerequisites.h"
+#include "BsFBXImportData.h"
+
+namespace BansheeEngine
+{
+	/**
+	 * @brief	Various helper methods to use during FBX import.
+	 */
+	class FBXUtility
+	{
+	public:
+		/**
+		 * @brief	Calculates per-index normals based on the provided smoothing groups.
+		 */
+		static void normalsFromSmoothing(const Vector<Vector4>& positions, const Vector<int>& indices,
+			const Vector<int>& smoothing, Vector<Vector4>& normals);
+
+		/**
+		 * @brief	Find vertices in the source mesh that have different attributes but have the same
+		 *			indexes, and splits them into two or more vertexes. Mesh with split vertices
+		 *			is output to "dest".
+		 *
+		 * @param	source	Source mesh to perform the split on. It's expected the position
+		 *					values are per-vertex, and all other attributes are per-index.
+		 * @param	dest	Output mesh with split vertices. Both vertex positions and
+		 *					attributes are per-vertex.
+		 */
+		static void splitVertices(const FBXImportMesh& source, FBXImportMesh& dest);
+
+		/**
+		 * @brief	Flips the triangle window order for all the triangles in the mesh.
+		 */
+		static void flipWindingOrder(FBXImportMesh& input);
+
+	private:
+		/**
+		 * @brief	Checks if vertex attributes at the specified indexes are similar enough, or
+		 *			does the vertex require a split.
+		 */
+		static bool needsSplitAttributes(const FBXImportMesh& meshA, int idxA, const FBXImportMesh& meshB, int idxB);
+
+		/**
+		 * @brief	Copies vertex attributes from the source mesh at the specified index, 
+		 *			to the destination mesh at the specified index.
+		 */
+		static void copyVertexAttributes(const FBXImportMesh& srcMesh, int srcIdx, FBXImportMesh& destMesh, int dstIdx);
+
+		/**
+		 * @brief	Adds a new vertex to the destination mesh and copies the vertex from the source mesh at the
+		 *			vertex index, and vertex attributes from the source mesh at the source index.
+		 */
+		static void addVertex(const FBXImportMesh& srcMesh, int srcIdx, int srcVertex, FBXImportMesh& destMesh);
+	};
+}

+ 23 - 0
BansheeFBXImporter/Source/BsFBXImportData.cpp

@@ -0,0 +1,23 @@
+#include "BsFBXImportData.h"
+
+namespace BansheeEngine
+{
+	FBXImportNode::~FBXImportNode()
+	{
+		for (auto& child : children)
+			bs_delete(child);
+	}
+
+	FBXImportScene::FBXImportScene()
+		:rootNode(nullptr)
+	{ }
+
+	FBXImportScene::~FBXImportScene()
+	{
+		if (rootNode != nullptr)
+			bs_delete(rootNode);
+
+		for (auto& mesh : meshes)
+			bs_delete(mesh);
+	}
+}

+ 565 - 375
BansheeFBXImporter/Source/BsFBXImporter.cpp

@@ -8,12 +8,66 @@
 #include "BsVector2.h"
 #include "BsVector3.h"
 #include "BsVector4.h"
+#include "BsQuaternion.h"
 #include "BsVertexDataDesc.h"
+#include "BsFBXUtility.h"
+#include <BsMeshImportOptions.h>
 
 namespace BansheeEngine
 {
+	Vector4 FBXToNativeType(const FbxVector4& value)
+	{
+		Vector4 native;
+		native.x = (float)value[0];
+		native.y = (float)value[1];
+		native.z = (float)value[2];
+		native.w = (float)value[3];
+
+		return native;
+	}
+
+	Vector3 FBXToNativeType(const FbxDouble3& value)
+	{
+		Vector3 native;
+		native.x = (float)value[0];
+		native.y = (float)value[1];
+		native.z = (float)value[2];
+
+		return native;
+	}
+
+	Vector2 FBXToNativeType(const FbxVector2& value)
+	{
+		Vector2 native;
+		native.x = (float)value[0];
+		native.y = (float)value[1];
+
+		return native;
+	}
+
+	RGBA FBXToNativeType(const FbxColor& value)
+	{
+		Color native;
+		native.r = (float)value[0];
+		native.g = (float)value[1];
+		native.b = (float)value[2];
+		native.a = (float)value[3];
+
+		return native.getAsRGBA();
+	}
+
+	FbxSurfaceMaterial* FBXToNativeType(FbxSurfaceMaterial* const& value)
+	{
+		return value;
+	}
+
+	int FBXToNativeType(const int & value)
+	{
+		return value;
+	}
+
 	FBXImporter::FBXImporter()
-		:SpecificImporter() 
+		:SpecificImporter(), mFBXManager(nullptr)
 	{
 		mExtensions.push_back(L"fbx");
 	}
@@ -38,18 +92,38 @@ namespace BansheeEngine
 
 	ResourcePtr FBXImporter::import(const Path& filePath, ConstImportOptionsPtr importOptions)
 	{
-		FbxManager* fbxManager = nullptr;
 		FbxScene* fbxScene = nullptr;
 
-		startUpSdk(fbxManager, fbxScene);
-		loadScene(fbxManager, fbxScene, filePath);
+		if (!startUpSdk(fbxScene))
+			return nullptr;
+
+		if (!loadFBXFile(fbxScene, filePath))
+			return nullptr;
 
+		const MeshImportOptions* meshImportOptions = static_cast<const MeshImportOptions*>(importOptions.get());
+		FBXImportOptions fbxImportOptions;
+		fbxImportOptions.importNormals = meshImportOptions->getImportNormals();
+		fbxImportOptions.importTangents = meshImportOptions->getImportTangents();
+		fbxImportOptions.importAnimation = meshImportOptions->getImportAnimation();
+		fbxImportOptions.importBlendShapes = meshImportOptions->getImportBlendShapes();
+		fbxImportOptions.importSkin = meshImportOptions->getImportSkin();
+
+		FBXImportScene importedScene;
+		parseScene(fbxScene, fbxImportOptions, importedScene);
+		splitMeshVertices(importedScene);
+		
 		Vector<SubMesh> subMeshes;
-		MeshDataPtr meshData = parseScene(fbxManager, fbxScene, subMeshes);	
+		MeshDataPtr meshData = generateMeshData(importedScene, subMeshes);
 
-		shutDownSdk(fbxManager);
+		// TODO - Later: Optimize mesh: Remove bad and degenerate polygons, optimize for vertex cache
 
-		MeshPtr mesh = Mesh::_createPtr(meshData, subMeshes);
+		shutDownSdk();
+
+		INT32 usage = MU_STATIC;
+		if (meshImportOptions->getCPUReadable())
+			usage |= MU_CPUCACHED;
+
+		MeshPtr mesh = Mesh::_createPtr(meshData, subMeshes, usage);
 
 		WString fileName = filePath.getWFilename(false);
 		mesh->setName(fileName);
@@ -57,53 +131,54 @@ namespace BansheeEngine
 		return mesh;
 	}
 
-	void FBXImporter::startUpSdk(FbxManager*& manager, FbxScene*& scene)
+	bool FBXImporter::startUpSdk(FbxScene*& scene)
 	{
-		// TODO Low priority - Initialize allocator methods for FBX. It calls a lot of heap allocs (200 000 calls for a simple 2k poly mesh) which slows down the import.
-		// Custom allocator would help a lot.
+		mFBXManager = FbxManager::Create();
+		if (mFBXManager == nullptr)
+		{
+			LOGERR("FBX import failed: FBX SDK failed to initialize. FbxManager::Create() failed.");
+			return false;
+		}
 
-		manager = FbxManager::Create();
-		if(manager == nullptr)
-			BS_EXCEPT(InternalErrorException, "FBX SDK failed to initialize. FbxManager::Create() failed.");
+		FbxIOSettings* ios = FbxIOSettings::Create(mFBXManager, IOSROOT);
+		mFBXManager->SetIOSettings(ios);
 
-		FbxIOSettings* ios = FbxIOSettings::Create(manager, IOSROOT);
-		manager->SetIOSettings(ios);
+		scene = FbxScene::Create(mFBXManager, "Import Scene");
+		if (scene == nullptr)
+		{
+			LOGWRN("FBX import failed: Failed to create FBX scene.");
+			return false;
+		}
 
-		scene = FbxScene::Create(manager, "Import Scene");
-		if(scene == nullptr)
-			BS_EXCEPT(InternalErrorException, "Failed to create FBX scene.");
+		return true;
 	}
 
-	void FBXImporter::shutDownSdk(FbxManager* manager)
+	void FBXImporter::shutDownSdk()
 	{
-		manager->Destroy();
+		mFBXManager->Destroy();
+		mFBXManager = nullptr;
 	}
 
-	void FBXImporter::loadScene(FbxManager* manager, FbxScene* scene, const Path& filePath)
+	bool FBXImporter::loadFBXFile(FbxScene* scene, const Path& filePath)
 	{
 		int lFileMajor, lFileMinor, lFileRevision;
 		int lSDKMajor,  lSDKMinor,  lSDKRevision;
 		FbxManager::GetFileFormatVersion(lSDKMajor, lSDKMinor, lSDKRevision);
 
-		FbxImporter* importer = FbxImporter::Create(manager, "");
-		bool importStatus = importer->Initialize(filePath.toString().c_str(), -1, manager->GetIOSettings());
+		FbxImporter* importer = FbxImporter::Create(mFBXManager, "");
+		bool importStatus = importer->Initialize(filePath.toString().c_str(), -1, mFBXManager->GetIOSettings());
 		
 		importer->GetFileVersion(lFileMajor, lFileMinor, lFileRevision);
 
 		if(!importStatus)
 		{
-			BS_EXCEPT(InternalErrorException, "Call to FbxImporter::Initialize() failed.\n" +
+			LOGERR("FBX import failed: Call to FbxImporter::Initialize() failed.\n" +
 				String("Error returned: %s\n\n") + String(importer->GetStatus().GetErrorString()));
+			return false;
 		}
 
-		manager->GetIOSettings()->SetBoolProp(IMP_FBX_ANIMATION, false);
-		manager->GetIOSettings()->SetBoolProp(IMP_FBX_TEXTURE, false);
-		manager->GetIOSettings()->SetBoolProp(IMP_FBX_LINK, false);
-		manager->GetIOSettings()->SetBoolProp(IMP_FBX_GOBO, false);
-		manager->GetIOSettings()->SetBoolProp(IMP_FBX_SHAPE, false);
-
-		// TODO - Parse animations
-		// TODO - Parse blend shapes
+		mFBXManager->GetIOSettings()->SetBoolProp(IMP_FBX_TEXTURE, false);
+		mFBXManager->GetIOSettings()->SetBoolProp(IMP_FBX_GOBO, false);
 
 		importStatus = importer->Import(scene);
 
@@ -111,24 +186,31 @@ namespace BansheeEngine
 		{
 			importer->Destroy();
 			
-			BS_EXCEPT(InternalErrorException, "Call to FbxImporter::Initialize() failed.\n" +
+			LOGERR("FBX import failed: Call to FbxImporter::Initialize() failed.\n" +
 				String("Error returned: %s\n\n") + String(importer->GetStatus().GetErrorString()));
+			return false;
 		}
 
+		FbxAxisSystem fileCoordSystem = scene->GetGlobalSettings().GetAxisSystem();
+		FbxAxisSystem bsCoordSystem(FbxAxisSystem::eYAxis, FbxAxisSystem::eParityOdd, FbxAxisSystem::eRightHanded);
+		if (fileCoordSystem != bsCoordSystem)
+			bsCoordSystem.ConvertScene(scene);
+
 		importer->Destroy();
+		return true;
 	}
 
-	MeshDataPtr FBXImporter::parseScene(FbxManager* manager, FbxScene* scene, Vector<SubMesh>& subMeshes)
+	void FBXImporter::parseScene(FbxScene* scene, const FBXImportOptions& options, FBXImportScene& outputScene)
 	{
+		outputScene.rootNode = createImportNode(outputScene, scene->GetRootNode(), nullptr);
+
 		Stack<FbxNode*> todo;
 		todo.push(scene->GetRootNode());
 
-		Vector<MeshDataPtr> allMeshes;
-		Vector<Vector<SubMesh>> allSubMeshes;
-
 		while(!todo.empty())
 		{
 			FbxNode* curNode = todo.top();
+			FBXImportNode* curImportNode = outputScene.nodeMap[curNode];
 			todo.pop();
 
 			const char* name = curNode->GetName();
@@ -140,6 +222,22 @@ namespace BansheeEngine
 
 				switch(attribType)
 				{
+				case FbxNodeAttribute::eNurbs:
+				case FbxNodeAttribute::eNurbsSurface:
+				case FbxNodeAttribute::ePatch:
+				{
+					FbxGeometryConverter geomConverter(mFBXManager);
+					attrib = geomConverter.Triangulate(attrib, true);
+
+					if (attrib->GetAttributeType() == FbxNodeAttribute::eMesh)
+					{
+						FbxMesh* mesh = static_cast<FbxMesh*>(attrib);
+						mesh->RemoveBadPolygons();
+
+						parseMesh(mesh, curImportNode, options, outputScene);
+					}
+				}
+					break;
 				case FbxNodeAttribute::eMesh:
 					{
 						FbxMesh* mesh = static_cast<FbxMesh*>(attrib);
@@ -147,475 +245,567 @@ namespace BansheeEngine
 
 						if(!mesh->IsTriangleMesh())
 						{
-							FbxGeometryConverter geomConverter(manager);
+							FbxGeometryConverter geomConverter(mFBXManager);
 							geomConverter.Triangulate(mesh, true);
 							attrib = curNode->GetNodeAttribute();
 							mesh = static_cast<FbxMesh*>(attrib);
 						}
 
-						allSubMeshes.push_back(Vector<SubMesh>());
-
-						MeshDataPtr meshData = parseMesh(mesh, allSubMeshes.back()); 
-						allMeshes.push_back(meshData);
-
-						// TODO - Transform meshes based on node transform
+						parseMesh(mesh, curImportNode, options, outputScene);
 					}
 					break;
-				case FbxNodeAttribute::eSkeleton:
-					break; // TODO - I should probably implement skeleton parsing
-
 				}
 			}
 
-			for(int i = 0; i < curNode->GetChildCount(); i++)
-				todo.push(curNode->GetChild(i));
-		}
-
-		if(allMeshes.size() == 0)
-			return nullptr;
-		else if(allMeshes.size() == 1)
-		{
-			subMeshes = allSubMeshes[0];
+			for (int i = 0; i < curNode->GetChildCount(); i++)
+			{
+				FbxNode* childNode = curNode->GetChild(i);
+				createImportNode(outputScene, childNode, curImportNode);
 
-			return allMeshes[0];
+				todo.push(childNode);
+			}
 		}
-		else
-		{
-			MeshDataPtr combinedMeshData = MeshData::combine(allMeshes, allSubMeshes, subMeshes);
 
-			return combinedMeshData;
-		}
+		// TODO - Parse skin
+		// TODO - Parse animation
+		// TODO - Parse blend shapes
 	}
 
-	MeshDataPtr FBXImporter::parseMesh(FbxMesh* mesh, Vector<SubMesh>& subMeshes, bool createTangentsIfMissing)
+	FBXImportNode* FBXImporter::createImportNode(FBXImportScene& scene, FbxNode* fbxNode, FBXImportNode* parent)
 	{
-		if (!mesh->GetNode())
-		{
-			VertexDataDescPtr tmpVertDesc = bs_shared_ptr<VertexDataDesc>();
-			return bs_shared_ptr<MeshData, ScratchAlloc>(0, 0, tmpVertDesc);
-		}
+		FBXImportNode* node = bs_new<FBXImportNode>();
 
-		// Find out which vertex attributes exist
-		bool hasColor = mesh->GetElementVertexColorCount() > 0;
-		const FbxGeometryElementVertexColor* colorElement = nullptr;
-		FbxGeometryElement::EMappingMode colorMappingMode = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode colorRefMode = FbxGeometryElement::eDirect;
+		Vector3 translation = FBXToNativeType(fbxNode->LclTranslation.Get());
+		Vector3 rotationEuler = FBXToNativeType(fbxNode->LclRotation.Get());
+		Vector3 scale = FBXToNativeType(fbxNode->LclScaling.Get());
 
-		if(hasColor)
+		Quaternion rotation((Radian)rotationEuler.x, (Radian)rotationEuler.y, (Radian)rotationEuler.z);
+
+		node->localTransform.setTRS(translation, rotation, scale);
+
+		if (parent != nullptr)
 		{
-			colorElement = mesh->GetElementVertexColor(0);
-			colorMappingMode = colorElement->GetMappingMode();
-			colorRefMode = colorElement->GetReferenceMode();
+			node->worldTransform = node->localTransform * parent->worldTransform;
 
-			if (colorMappingMode == FbxGeometryElement::eNone)
-				hasColor = false;
+			parent->children.push_back(node);
 		}
+		else
+			node->worldTransform = node->localTransform;
 
-		bool hasNormal = mesh->GetElementNormalCount() > 0;
-		const FbxGeometryElementNormal* normalElement = nullptr;
-		FbxGeometryElement::EMappingMode normalMappingMode = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode normalRefMode = FbxGeometryElement::eDirect;
+		scene.nodeMap.insert(std::make_pair(fbxNode, node));
 
-		if (hasNormal)
+		return node;
+	}
+
+	void FBXImporter::splitMeshVertices(FBXImportScene& scene)
+	{
+		Vector<FBXImportMesh*> splitMeshes;
+
+		for (auto& mesh : scene.meshes)
 		{
-			normalElement = mesh->GetElementNormal(0);
-			normalMappingMode = normalElement->GetMappingMode();
-			normalRefMode = normalElement->GetReferenceMode();
+			FBXImportMesh* splitMesh = bs_new<FBXImportMesh>();
+			FBXUtility::splitVertices(*mesh, *splitMesh);
+			FBXUtility::flipWindingOrder(*splitMesh);
+			splitMeshes.push_back(splitMesh);
 
-			if (normalMappingMode == FbxGeometryElement::eNone)
-				hasNormal = false;
+			bs_delete(mesh);
 		}
 
-		bool hasTangent = mesh->GetElementTangentCount() > 0;
-		const FbxGeometryElementTangent* tangentElement = nullptr;
-		FbxGeometryElement::EMappingMode tangentMappingMode = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode tangentRefMode = FbxGeometryElement::eDirect;
+		scene.meshes = splitMeshes;
+	}
 
-		if (hasTangent)
+	MeshDataPtr FBXImporter::generateMeshData(const FBXImportScene& scene, Vector<SubMesh>& outputSubMeshes)
+	{
+		Vector<MeshDataPtr> allMeshData;
+		Vector<Vector<SubMesh>> allSubMeshes;
+
+		for (auto& mesh : scene.meshes)
 		{
-			tangentElement = mesh->GetElementTangent(0);
-			tangentMappingMode = tangentElement->GetMappingMode();
-			tangentRefMode = tangentElement->GetReferenceMode();
+			Vector<Vector<UINT32>> indicesPerMaterial;
+			for (UINT32 i = 0; i < (UINT32)mesh->indices.size(); i++)
+			{
+				while (mesh->materials[i] >= indicesPerMaterial.size())
+					indicesPerMaterial.push_back(Vector<UINT32>());
 
-			if (tangentMappingMode == FbxGeometryElement::eNone)
-				hasTangent = false;
-		}
+				indicesPerMaterial[mesh->materials[i]].push_back(mesh->indices[i]);
+			}
 
-		bool hasBitangent = mesh->GetElementBinormalCount() > 0;
-		const FbxGeometryElementBinormal* bitangentElement = nullptr;
-		FbxGeometryElement::EMappingMode bitangentMappingMode = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode bitangentRefMode = FbxGeometryElement::eDirect;
+			UINT32* orderedIndices = (UINT32*)bs_alloc((UINT32)mesh->indices.size() * sizeof(UINT32));
+			Vector<SubMesh> subMeshes;
+			UINT32 currentIndex = 0;
 
-		if (hasBitangent)
-		{
-			bitangentElement = mesh->GetElementBinormal(0);
-			bitangentMappingMode = bitangentElement->GetMappingMode();
-			bitangentRefMode = bitangentElement->GetReferenceMode();
+			for (auto& subMeshIndices : indicesPerMaterial)
+			{
+				UINT32 indexCount = (UINT32)subMeshIndices.size();
+				UINT32* dest = orderedIndices + currentIndex;
+				memcpy(dest, subMeshIndices.data(), indexCount * sizeof(UINT32));
 
-			if (bitangentMappingMode == FbxGeometryElement::eNone)
-				hasBitangent = false;
-		}
+				subMeshes.push_back(SubMesh(currentIndex, indexCount, DOT_TRIANGLE_LIST));
 
-		bool hasUV0 = mesh->GetElementUVCount() > 0;
-		const FbxGeometryElementUV* UVElement0 = nullptr;
-		FbxGeometryElement::EMappingMode UVMappingMode0 = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode UVRefMode0 = FbxGeometryElement::eDirect;
+				currentIndex += indexCount;
+			}
 
-		if (hasUV0)
-		{
-			UVElement0 = mesh->GetElementUV(0);
-			UVMappingMode0 = UVElement0->GetMappingMode();
-			UVRefMode0 = UVElement0->GetReferenceMode();
+			VertexDataDescPtr vertexDesc = bs_shared_ptr<VertexDataDesc>();
+			vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
 
-			if (UVMappingMode0 == FbxGeometryElement::eNone)
-				hasUV0 = false;
-		}
+			size_t numVertices = mesh->positions.size();
+			bool hasColors = mesh->colors.size() == numVertices;
+			bool hasNormals = mesh->normals.size() == numVertices;
 
-		bool hasUV1 = mesh->GetElementUVCount() > 1;
-		const FbxGeometryElementUV* UVElement1 = nullptr;
-		FbxGeometryElement::EMappingMode UVMappingMode1 = FbxGeometryElement::eNone;
-		FbxGeometryElement::EReferenceMode UVRefMode1 = FbxGeometryElement::eDirect;
+			if (hasColors)
+				vertexDesc->addVertElem(VET_COLOR, VES_COLOR);
 
-		if (hasUV1)
-		{
-			UVElement1 = mesh->GetElementUV(1);
-			UVMappingMode1 = UVElement1->GetMappingMode();
-			UVRefMode1 = UVElement1->GetReferenceMode();
+			bool hasTangents = false;
+			if (hasNormals)
+			{
+				vertexDesc->addVertElem(VET_FLOAT3, VES_NORMAL);
 
-			if (UVMappingMode1 == FbxGeometryElement::eNone)
-				hasUV1 = false;
-		}
+				if (mesh->tangents.size() == numVertices &&
+					mesh->bitangents.size() == numVertices)
+				{
+					vertexDesc->addVertElem(VET_FLOAT4, VES_TANGENT);
+					hasTangents = true;
+				}
+			}
 
-		// Create tangents if needed
-		if(createTangentsIfMissing && mesh->GetElementUVCount() > 0)
-			mesh->GenerateTangentsData(0, false);
+			int UVIdx = 0;
+			for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+			{
+				if (mesh->UV[i].size() == numVertices)
+				{
+					vertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD, UVIdx++);
+				}
+			}
+
+			UINT32 numIndices = (UINT32)mesh->indices.size();
+			for (auto& node : mesh->referencedBy)
+			{
+				Matrix4 worldTransform = node->worldTransform;
+				Matrix4 worldTransformIT = worldTransform.transpose();
+				worldTransformIT = worldTransformIT.inverse();
 
-		// Calculate number of vertices and indexes
-		int polygonCount = mesh->GetPolygonCount();
-		UINT32 vertexCount = 0;
+				MeshDataPtr meshData = bs_shared_ptr<MeshData>((UINT32)numVertices, numIndices, vertexDesc);
 
-		VertexDataDescPtr vertexDesc = bs_shared_ptr<VertexDataDesc>();
+				// Copy indices
+				UINT32* indices = meshData->getIndices32();
+				memcpy(indices, mesh->indices.data(), numIndices * sizeof(UINT32));
 
-		vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
+				// Copy & transform positions
+				auto posIter = meshData->getVec3DataIter(VES_POSITION, 0);
 
-		if(hasColor)
-			vertexDesc->addVertElem(VET_COLOR, VES_COLOR);
+				for (auto& position : mesh->positions)
+				{
+					Vector3 tfrmdValue = worldTransform.multiplyAffine((Vector3)position);
+					posIter.addValue(tfrmdValue);
+				}
 
-		if(hasNormal)
-			vertexDesc->addVertElem(VET_FLOAT3, VES_NORMAL);
+				// Copy & transform normals
+				if (hasNormals)
+				{
+					auto normalIter = meshData->getVec3DataIter(VES_NORMAL, 0);
 
-		if(hasTangent)
-			vertexDesc->addVertElem(VET_FLOAT3, VES_TANGENT);
+					// Copy, convert & transform tangents & bitangents
+					if (hasTangents)
+					{
+						auto tangentIter = meshData->getVec4DataIter(VES_TANGENT, 0);
 
-		if(hasBitangent)
-			vertexDesc->addVertElem(VET_FLOAT3, VES_BITANGENT);
+						for (UINT32 i = 0; i < (UINT32)numVertices; i++)
+						{
+							Vector3 normal = (Vector3)mesh->normals[i];
+							normal = worldTransformIT.multiplyAffine(normal);
+							normalIter.addValue(normal);
 
-		if (hasUV0)
-			vertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD, 0);
+							Vector3 tangent = (Vector3)mesh->tangents[i];
+							tangent = worldTransformIT.multiplyAffine(tangent);
 
-		if (hasUV1)
-			vertexDesc->addVertElem(VET_FLOAT2, VES_TEXCOORD, 1);
+							Vector3 bitangent = (Vector3)mesh->bitangents[i];
+							bitangent = worldTransformIT.multiplyAffine(bitangent);
 
-		// Count the polygon count of each material
-		FbxLayerElementArrayTemplate<int>* materialElementArray = NULL;
-		FbxGeometryElement::EMappingMode materialMappingMode = FbxGeometryElement::eNone;
+							Vector3 engineBitangent = Vector3::cross(normal, tangent);
+							float sign = Vector3::dot(engineBitangent, bitangent);
 
-		Set<UINT32> uniqueIndices;
-		UINT32 numIndices = 0;
-		if (mesh->GetElementMaterial())
-		{
-			materialElementArray = &mesh->GetElementMaterial()->GetIndexArray();
-			materialMappingMode = mesh->GetElementMaterial()->GetMappingMode();
-			if (materialElementArray && materialMappingMode == FbxGeometryElement::eByPolygon)
-			{
-				FBX_ASSERT(materialElementArray->GetCount() == polygonCount);
-				if (materialElementArray->GetCount() == polygonCount)
-				{
-					// Count the faces of each material
-					for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
+							Vector4 combinedTangent(tangent.x, tangent.y, tangent.z, sign > 0 ? 1.0f : -1.0f);
+							tangentIter.addValue(combinedTangent);
+						}
+					}
+					else // Just normals
 					{
-						const UINT32 materialIndex = (UINT32)materialElementArray->GetAt(polygonIndex);
-						if (subMeshes.size() < materialIndex + 1)
+						for (auto& normal : mesh->normals)
 						{
-							subMeshes.resize(materialIndex + 1);
+							Vector3 tfrmdValue = worldTransformIT.multiplyAffine((Vector3)normal);
+							normalIter.addValue(tfrmdValue);
 						}
-
-						subMeshes[materialIndex].indexCount += 3;
 					}
+				}
+
+				// Copy colors
+				if (hasColors)
+				{
+					auto colorIter = meshData->getDWORDDataIter(VES_COLOR, 0);
+
+					for (auto& color : mesh->colors)
+						colorIter.addValue(color);
+				}
 
-					// Record the offsets and allocate index arrays
-					UINT32 materialCount = (UINT32)subMeshes.size();
-					int offset = 0;
-					for (UINT32 i = 0; i < materialCount; ++i)
+				// Copy UV
+				int writeUVIDx = 0;
+				for (auto& uvLayer : mesh->UV)
+				{
+					if (uvLayer.size() == numVertices)
 					{
-						subMeshes[i].indexOffset = offset;
-						offset += subMeshes[i].indexCount;
+						auto uvIter = meshData->getVec2DataIter(VES_TEXCOORD, writeUVIDx);
+
+						for (auto& uv : uvLayer)
+						{
+							uv.y = 1.0f - uv.y;
+							uvIter.addValue(uv);
+						}
 
-						numIndices += subMeshes[i].indexCount;
+						writeUVIDx++;
 					}
-					FBX_ASSERT(offset == polygonCount * 3);
 				}
+
+				allMeshData.push_back(meshData);
+				allSubMeshes.push_back(subMeshes);
 			}
 		}
 
-		if (subMeshes.size() == 0)
+		if (allMeshData.size() > 1)
 		{
-			numIndices = polygonCount * 3;
-
-			subMeshes.resize(1);
-			subMeshes[0].indexCount = polygonCount * 3;
+			return MeshData::combine(allMeshData, allSubMeshes, outputSubMeshes);
 		}
-
-		// Count number of unique vertices
-		for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
+		else if (allMeshData.size() == 1)
 		{
-			for (int vertexIndex = 0; vertexIndex < 3; ++vertexIndex)
-			{
-				const int controlPointIndex = mesh->GetPolygonVertex(polygonIndex, vertexIndex);
-				if (uniqueIndices.find(controlPointIndex) == uniqueIndices.end())
-				{
-					uniqueIndices.insert(controlPointIndex);
-					vertexCount++;
-				}
-			}
+			outputSubMeshes = allSubMeshes[0];
+			return allMeshData[0];
 		}
 
-		// Allocate the array memory for all vertices and indices
-		MeshDataPtr meshData = bs_shared_ptr<MeshData, ScratchAlloc>(vertexCount, numIndices, vertexDesc);
-
-		VertexElemIter<Vector3> positions = meshData->getVec3DataIter(VES_POSITION);
-
-		VertexElemIter<UINT32> colors;
-		if(hasColor)
-			colors = meshData->getDWORDDataIter(VES_COLOR);
+		return nullptr;
+	}
 
-		VertexElemIter<Vector3> normals;
-		if (hasNormal)
-			normals = meshData->getVec3DataIter(VES_NORMAL);
+	template<class TFBX, class TNative>
+	class FBXDirectIndexer
+	{
+	public:
+		FBXDirectIndexer(const FbxLayerElementTemplate<TFBX>& layer)
+			:mElementArray(layer.GetDirectArray()),
+			mElementCount(mElementArray.GetCount())
+		{}
 
-		VertexElemIter<Vector3> tangents;
-		if (hasTangent)
-			tangents = meshData->getVec3DataIter(VES_TANGENT);
+		bool get(int index, TNative& output) const
+		{
+			if (index < 0 || index >= mElementCount)
+				return false;
 
-		VertexElemIter<Vector3> bitangents;
-		if (hasBitangent)
-			bitangents = meshData->getVec3DataIter(VES_BITANGENT);
+			output = FBXToNativeType(mElementArray.GetAt(index));
+			return true;
+		}
 
-		VertexElemIter<Vector2> uv0;
-		if (hasUV0)
-			uv0 = meshData->getVec2DataIter(VES_TEXCOORD, 0);
+		bool isEmpty() const
+		{
+			return mElementCount == 0;
+		}
 
-		VertexElemIter<Vector2> uv1;
-		if (hasUV1)
-			uv1 = meshData->getVec2DataIter(VES_TEXCOORD, 1);
+	private:
+		const FbxLayerElementArrayTemplate<TFBX>& mElementArray;
+		int mElementCount;
+	};
 
-		// Get node transform
-		FbxAMatrix worldTransform;
-		FbxAMatrix worldTransformIT;
-		worldTransform = computeWorldTransform(mesh->GetNode());
-		worldTransformIT = worldTransform.Inverse();
-		worldTransformIT = worldTransformIT.Transpose();
+	template<class TFBX, class TNative>
+	class FBXIndexIndexer
+	{
+	public:
+		FBXIndexIndexer(const FbxLayerElementTemplate<TFBX>& layer)
+			:mElementArray(layer.GetDirectArray()),
+			mIndexArray(layer.GetIndexArray()),
+			mElementCount(mElementArray.GetCount()),
+			mIndexCount(mIndexArray.GetCount())
+		{}
+
+		bool get(int index, TNative& output) const
+		{
+			if (index < 0 || index >= mIndexCount)
+				return false;
 
-		// Populate the mesh data with vertex attributes and indices
-		const FbxVector4* controlPoints = mesh->GetControlPoints();
+			int actualIndex = mIndexArray.GetAt(index);
 
-		Vector<UINT32> indexOffsetPerSubmesh;
-		indexOffsetPerSubmesh.resize(subMeshes.size(), 0);
+			if (actualIndex < 0 || actualIndex >= mElementCount)
+				return false;
 
-		Vector<UINT32*> indices;
-		indices.resize(subMeshes.size());
+			output = FBXToNativeType(mElementArray.GetAt(actualIndex));
+			return true;
+		}
 
-		for(UINT32 i = 0; i < (UINT32)indices.size(); i++)
+		bool isEmpty() const
 		{
-			indices[i] = meshData->getIndices32() + subMeshes[i].indexOffset;
+			return mElementCount == 0 || mIndexCount == 0;
 		}
 
-		Map<UINT32, UINT32> indexMap;
+	private:
+		const FbxLayerElementArrayTemplate<TFBX>& mElementArray;
+		const FbxLayerElementArrayTemplate<int>& mIndexArray;
+		int mElementCount;
+		int mIndexCount;
+	};
+
+	template<class TFBX, class TNative, class TIndexer>
+	void readLayerData(FbxLayerElementTemplate<TFBX>& layer, Vector<TNative>& output, const Vector<int>& indices)
+	{
+		TIndexer indexer(layer);
+		if (indexer.isEmpty())
+			return;
+
+		output.resize(indices.size());
+
+		FbxLayerElement::EMappingMode mappingMode = layer.GetMappingMode();
 
-		UINT32 curVertexCount = 0;
-		for (int polygonIndex = 0; polygonIndex < polygonCount; ++polygonIndex)
+		UINT32 indexCount = (UINT32)indices.size();
+		switch (mappingMode)
 		{
-			// The material for current face.
-			int lMaterialIndex = 0;
-			if (materialElementArray && materialMappingMode == FbxGeometryElement::eByPolygon)
+		case FbxLayerElement::eByControlPoint:
+			for (UINT32 i = 0; i < indexCount; i++)
 			{
-				lMaterialIndex = materialElementArray->GetAt(polygonIndex);
+				int index = indices[i];
+				indexer.get(index, output[i]);
 			}
+			break;
+		case FbxLayerElement::eByPolygonVertex:
+			for (UINT32 i = 0; i < indexCount; i++)
+				indexer.get(i, output[i]);
+			break;
+		case FbxLayerElement::eByPolygon:
+			// We expect mesh to be triangulated here
+		{
+			UINT32 polygonCount = indexCount / 3;
+			UINT32 index = 0;
 
-			// Where should I save the vertex attribute index, according to the material
-			int indexOffset = subMeshes[lMaterialIndex].indexOffset + indexOffsetPerSubmesh[lMaterialIndex];
-			for (int vertexIndex = 0; vertexIndex < 3; ++vertexIndex)
+			for (UINT32 i = 0; i < polygonCount; i++)
 			{
-				int controlPointIndex = mesh->GetPolygonVertex(polygonIndex, vertexIndex);
-				int triangleIndex = indexOffset + (2 - vertexIndex);
-
-				auto findIter = indexMap.find(controlPointIndex);
-				if (findIter != indexMap.end())
-				{
-					indices[lMaterialIndex][triangleIndex] = static_cast<unsigned int>(findIter->second);
-				}
-				else
-				{
-					indices[lMaterialIndex][triangleIndex] = static_cast<unsigned int>(curVertexCount);
-					FbxVector4 currentVertex = controlPoints[controlPointIndex];
-					currentVertex = worldTransform.MultT(currentVertex);
-
-					Vector3 curPosValue;
-					curPosValue[0] = static_cast<float>(currentVertex[0]);
-					curPosValue[1] = static_cast<float>(currentVertex[1]);
-					curPosValue[2] = static_cast<float>(currentVertex[2]);
+				TNative value;
+				indexer.get(i, value);
 
-					positions.addValue(curPosValue);
-					indexMap[controlPointIndex] = curVertexCount;
-					++curVertexCount;
+				output[index++] = value;
+				output[index++] = value;
+				output[index++] = value;
+			}
+		}
+			break;
+		case FbxLayerElement::eAllSame:
+		{
+			TNative value;
+			indexer.get(0, value);
 
-					if (hasColor)
-					{
-						int colorIdx = indexOffset + vertexIndex;
+			for (UINT32 i = 0; i < indexCount; i++)
+				output[i] = value;
+		}
+			break;
+		default:
+			LOGWRN("FBX Import: Unsupported layer mapping mode.");
+			break;
+		}
+	}
 
-						if (colorMappingMode == FbxLayerElement::eByControlPoint)
-							colorIdx = controlPointIndex;
+	template<class TFBX, class TNative>
+	void readLayerData(FbxLayerElementTemplate<TFBX>& layer, Vector<TNative>& output, const Vector<int>& indices)
+	{
+		FbxLayerElement::EReferenceMode refMode = layer.GetReferenceMode();
 
-						if (colorRefMode == FbxLayerElement::eIndexToDirect)
-							colorIdx = colorElement->GetIndexArray().GetAt(colorIdx);
+		if (refMode == FbxLayerElement::eDirect)
+			readLayerData<TFBX, TNative, FBXDirectIndexer<TFBX, TNative> >(layer, output, indices);
+		else if (refMode == FbxLayerElement::eIndexToDirect)
+			readLayerData<TFBX, TNative, FBXIndexIndexer<TFBX, TNative> >(layer, output, indices);
+		else
+			LOGWRN("FBX Import: Unsupported layer reference mode.");
+	}
 
-						FbxColor lCurrentColor = colorElement->GetDirectArray().GetAt(colorIdx);
+	void FBXImporter::parseMesh(FbxMesh* mesh, FBXImportNode* parentNode, const FBXImportOptions& options, FBXImportScene& outputScene)
+	{
+		// Check if valid
+		if (!mesh->IsTriangleMesh())
+			return;
 
-						Color curColorValue;
-						curColorValue[0] = static_cast<float>(lCurrentColor[0]);
-						curColorValue[1] = static_cast<float>(lCurrentColor[1]);
-						curColorValue[2] = static_cast<float>(lCurrentColor[2]);
-						curColorValue[3] = static_cast<float>(lCurrentColor[3]);
+		UINT32 vertexCount = mesh->GetControlPointsCount();
+		UINT32 triangleCount = mesh->GetPolygonCount();
 
-						UINT32 color32 = curColorValue.getAsRGBA();
-						colors.addValue(color32);
-					}
+		if (vertexCount == 0 || triangleCount == 0)
+			return;
 
-					if (hasNormal)
-					{
-						int normalIdx = indexOffset + vertexIndex;
+		// Register in global mesh array
+		FBXImportMesh* importMesh = nullptr;
 
-						if (normalMappingMode == FbxLayerElement::eByControlPoint)
-							normalIdx = controlPointIndex;
+		auto iterFindMesh = outputScene.meshMap.find(mesh);
+		if (iterFindMesh != outputScene.meshMap.end())
+		{
+			UINT32 meshIdx = iterFindMesh->second;
+			outputScene.meshes[meshIdx]->referencedBy.push_back(parentNode);
 
-						if (normalRefMode == FbxLayerElement::eIndexToDirect)
-							normalIdx = normalElement->GetIndexArray().GetAt(normalIdx);
+			return;
+		}
+		else
+		{
+			importMesh = bs_new<FBXImportMesh>();
+			outputScene.meshes.push_back(importMesh);
 
-						FbxVector4 currentNormal = normalElement->GetDirectArray().GetAt(normalIdx);
-						currentNormal = worldTransformIT.MultT(currentNormal);
+			importMesh->referencedBy.push_back(parentNode);
+			outputScene.meshMap[mesh] = (UINT32)outputScene.meshes.size() - 1;
+		}
 
-						Vector3 curNormalValue;
-						curNormalValue[0] = static_cast<float>(currentNormal[0]);
-						curNormalValue[1] = static_cast<float>(currentNormal[1]);
-						curNormalValue[2] = static_cast<float>(currentNormal[2]);
+		// Import vertices
+		importMesh->positions.resize(vertexCount);
+		FbxVector4* controlPoints = mesh->GetControlPoints();
 
-						normals.addValue(curNormalValue);
-					}
+		for (UINT32 i = 0; i < vertexCount; i++)
+			importMesh->positions[i] = FBXToNativeType(controlPoints[i]);
 
-					if (hasTangent)
-					{
-						int tangentIdx = indexOffset + vertexIndex;
+		// Import triangles
+		UINT32 indexCount = triangleCount * 3;
+		importMesh->indices.resize(indexCount);
 
-						if (tangentMappingMode == FbxLayerElement::eByControlPoint)
-							tangentIdx = controlPointIndex;
+		int* fbxIndices = mesh->GetPolygonVertices();
+		importMesh->indices.assign(fbxIndices, fbxIndices + indexCount);
 
-						if (tangentRefMode == FbxLayerElement::eIndexToDirect)
-							tangentIdx = tangentElement->GetIndexArray().GetAt(tangentIdx);
+		// Import UVs
+		Vector<FbxLayerElementUV*> fbxUVLayers;
 
-						FbxVector4 currentTangent = tangentElement->GetDirectArray().GetAt(tangentIdx);
-						currentTangent = worldTransformIT.MultT(currentTangent);
-
-						Vector3 curTangentValue;
-						curTangentValue[0] = static_cast<float>(currentTangent[0]);
-						curTangentValue[1] = static_cast<float>(currentTangent[1]);
-						curTangentValue[2] = static_cast<float>(currentTangent[2]);
+		//// Search the diffuse layers first
+		for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+		{
+			FbxLayer* layer = mesh->GetLayer(i, FbxLayerElement::eUV);
+			if (layer == nullptr)
+				continue;
 
-						tangents.addValue(curTangentValue);
-					}
+			for (int j = FbxLayerElement::eTextureDiffuse; j < FbxLayerElement::eTypeCount; j++)
+			{
+				FbxLayerElementUV* uvLayer = layer->GetUVs((FbxLayerElement::EType)j);
+				if (uvLayer == nullptr)
+					continue;
 
-					if (hasBitangent)
-					{
-						int bitangentIdx = indexOffset + vertexIndex;
+				fbxUVLayers.push_back(uvLayer);
 
-						if (bitangentMappingMode == FbxLayerElement::eByControlPoint)
-							bitangentIdx = controlPointIndex;
+				if (fbxUVLayers.size() == FBX_IMPORT_MAX_UV_LAYERS)
+					break;
+			}
 
-						if (bitangentRefMode == FbxLayerElement::eIndexToDirect)
-							bitangentIdx = bitangentElement->GetIndexArray().GetAt(bitangentIdx);
+			if (fbxUVLayers.size() == FBX_IMPORT_MAX_UV_LAYERS)
+				break;
+		}
 
-						FbxVector4 currentBitangent = bitangentElement->GetDirectArray().GetAt(bitangentIdx);
-						currentBitangent = worldTransformIT.MultT(currentBitangent);
+		//// If there's room, search all others too
+		if (fbxUVLayers.size() < FBX_IMPORT_MAX_UV_LAYERS)
+		{
+			UINT32 numLayers = mesh->GetLayerCount();
+			for (UINT32 i = 0; i < numLayers; i++)
+			{
+				FbxLayer* layer = mesh->GetLayer(i);
+				if (layer == nullptr)
+					continue;
 
-						Vector3 curBitangentValue;
-						curBitangentValue[0] = static_cast<float>(currentBitangent[0]);
-						curBitangentValue[1] = static_cast<float>(currentBitangent[1]);
-						curBitangentValue[2] = static_cast<float>(currentBitangent[2]);
+				for (int j = FbxLayerElement::eTextureDiffuse; j < FbxLayerElement::eTypeCount; j++)
+				{
+					FbxLayerElementUV* uvLayer = layer->GetUVs((FbxLayerElement::EType)j);
+					if (uvLayer == nullptr)
+						continue;
 
-						bitangents.addValue(curBitangentValue);
-					}
+					auto iterFind = std::find(fbxUVLayers.begin(), fbxUVLayers.end(), uvLayer);
+					if (iterFind != fbxUVLayers.end())
+						continue;
 
-					if (hasUV0)
-					{
-						int uv0Idx = indexOffset + vertexIndex;
+					fbxUVLayers.push_back(uvLayer);
 
-						if (UVMappingMode0 == FbxLayerElement::eByControlPoint)
-							uv0Idx = controlPointIndex;
+					if (fbxUVLayers.size() == FBX_IMPORT_MAX_UV_LAYERS)
+						break;
+				}
 
-						if (UVRefMode0 == FbxLayerElement::eIndexToDirect)
-							uv0Idx = UVElement0->GetIndexArray().GetAt(uv0Idx);
+				if (fbxUVLayers.size() == FBX_IMPORT_MAX_UV_LAYERS)
+					break;
+			}
+		}
 
-						FbxVector4 currentUV = UVElement0->GetDirectArray().GetAt(uv0Idx);
+		for (size_t i = 0; i < fbxUVLayers.size(); i++)
+			readLayerData(*fbxUVLayers[i], importMesh->UV[i], importMesh->indices);
 
-						Vector2 curUV0Value;
-						curUV0Value[0] = static_cast<float>(currentUV[0]);
-						curUV0Value[1] = 1.0f - static_cast<float>(currentUV[1]);
+		FbxLayer* mainLayer = mesh->GetLayer(0);
+		if (mainLayer != nullptr)
+		{
+			// Import colors
+			if (mainLayer->GetVertexColors() != nullptr)
+				readLayerData(*mainLayer->GetVertexColors(), importMesh->colors, importMesh->indices);
 
-						uv0.addValue(curUV0Value);
-					}
+			// Import normals
+			if (options.importNormals)
+			{
+				bool hasNormals = mainLayer->GetNormals() != nullptr;
 
-					if (hasUV1)
+				if (!hasNormals)
+				{
+					if (mainLayer->GetSmoothing() != nullptr)
 					{
-						int uv1Idx = indexOffset + vertexIndex;
-
-						if (UVMappingMode1 == FbxLayerElement::eByControlPoint)
-							uv1Idx = controlPointIndex;
-
-						if (UVRefMode1 == FbxLayerElement::eIndexToDirect)
-							uv1Idx = UVElement1->GetIndexArray().GetAt(uv1Idx);
+						FbxLayerElementSmoothing* smoothing = mainLayer->GetSmoothing();
 
-						FbxVector4 currentUV = UVElement1->GetDirectArray().GetAt(uv1Idx);
+						if (smoothing->GetMappingMode() == FbxLayerElement::eByEdge)
+						{
+							FbxGeometryConverter converter(mFBXManager);
+							converter.ComputePolygonSmoothingFromEdgeSmoothing(mesh, 0);
+						}
 
-						Vector2 curUV1Value;
-						curUV1Value[0] = static_cast<float>(currentUV[0]);
-						curUV1Value[1] = 1.0f - static_cast<float>(currentUV[1]);
+						Vector<int> smoothingGroups;
+						readLayerData(*smoothing, smoothingGroups, importMesh->indices);
 
-						uv1.addValue(curUV1Value);
+						if (!smoothingGroups.empty())
+						{
+							FBXUtility::normalsFromSmoothing(importMesh->positions, importMesh->indices, smoothingGroups, importMesh->normals);
+							hasNormals = true;
+						}
 					}
 				}
+				
+				if (hasNormals)
+					readLayerData(*mainLayer->GetNormals(), importMesh->normals, importMesh->indices);
 			}
 
-			indexOffsetPerSubmesh[lMaterialIndex] += 3;
-		}
+			// Import tangents
+			if (options.importTangents)
+			{
+				bool hasTangents = mainLayer->GetTangents() != nullptr && mainLayer->GetBinormals() != nullptr;
 
-		return meshData;
-	}
+				if (!hasTangents)
+				{
+					if (fbxUVLayers.size() > 0)
+						hasTangents = mesh->GenerateTangentsData(0, false);
+				}
 
-	FbxAMatrix FBXImporter::computeWorldTransform(FbxNode* node)
-	{
-		FbxVector4 translation = node->GetGeometricTranslation(FbxNode::eSourcePivot);
-		FbxVector4 rotation = node->GetGeometricRotation(FbxNode::eSourcePivot);
-		FbxVector4 scaling = node->GetGeometricScaling(FbxNode::eSourcePivot);
+				if (hasTangents)
+				{
+					readLayerData(*mainLayer->GetTangents(), importMesh->tangents, importMesh->indices);
+					readLayerData(*mainLayer->GetBinormals(), importMesh->bitangents, importMesh->indices);
+				}
+			}
+
+			// Import material indexes
+			if (mainLayer->GetMaterials() != nullptr)
+			{
+				Vector<FbxSurfaceMaterial*> fbxMaterials;
 
-		FbxAMatrix localTransform;
-		localTransform.SetT(translation);
-		localTransform.SetR(rotation);
-		localTransform.SetS(scaling);
+				readLayerData(*mainLayer->GetMaterials(), fbxMaterials, importMesh->indices);
 
-		FbxAMatrix& globalTransform = node->EvaluateGlobalTransform();
+				UnorderedMap<FbxSurfaceMaterial*, int> materialLookup;
+				int nextMaterialIdx = 0;
+				for (UINT32 i = 0; i < (UINT32)fbxMaterials.size(); i++)
+				{
+					auto iterFind = materialLookup.find(fbxMaterials[i]);
 
-		FbxAMatrix worldTransform;
-		worldTransform = globalTransform * localTransform;
+					int materialIdx = 0;
+					if (iterFind != materialLookup.end())
+						materialIdx = iterFind->second;
+					else
+					{
+						materialIdx = nextMaterialIdx++;
+						materialLookup[fbxMaterials[i]] = materialIdx;
+					}
 
-		return worldTransform;
+					importMesh->materials.push_back(materialIdx);
+				}
+			}
+		}
 	}
 }

+ 314 - 0
BansheeFBXImporter/Source/BsFBXUtility.cpp

@@ -0,0 +1,314 @@
+#include "BsFBXUtility.h"
+#include "BsVector2.h"
+#include "BsVector3.h"
+#include "BsVector4.h"
+
+namespace BansheeEngine
+{
+	struct SmoothNormal
+	{
+		int group = 0;
+		Vector3 normal;
+
+		void addNormal(int group, const Vector3& normal)
+		{
+			this->group |= group;
+			this->normal += normal;
+		}
+
+		void addNormal(const SmoothNormal& other)
+		{
+			this->group |= other.group;
+			this->normal += other.normal;
+		}
+
+		void normalize()
+		{
+			normal.normalize();
+		}
+	};
+
+	struct SmoothVertex
+	{
+		Vector<SmoothNormal> normals;
+
+		void addNormal(int group, const Vector3& normal)
+		{
+			bool found = false;
+
+			for (size_t i = 0; i < normals.size(); i++)
+			{
+				if ((normals[i].group & group) != 0)
+				{
+					bool otherGroups = (group & ~normals[i].group) != 0;
+
+					if (otherGroups)
+					{
+						for (size_t j = i + 1; j < normals.size(); j++)
+						{
+							if ((normals[j].group & group) != 0)
+							{
+								normals[i].addNormal(normals[j]);
+								normals.erase(normals.begin() + j);
+								--j;
+							}
+						}
+					}
+
+					normals[i].addNormal(group, normal);
+
+					found = true;
+					break;;
+				}
+			}
+
+			if (!found)
+			{
+				SmoothNormal smoothNormal;
+				smoothNormal.addNormal(group, normal);
+
+				normals.push_back(smoothNormal);
+			}
+		}
+
+		Vector3 getNormal(int group) const
+		{
+			for (size_t i = 0; i < normals.size(); i++)
+			{
+				if (normals[i].group & group)
+					return normals[i].normal;
+			}
+
+			return Vector3::ZERO;
+		}
+
+		void normalize()
+		{
+			for (size_t i = 0; i < normals.size(); ++i)
+				normals[i].normalize();
+		}
+	};
+
+	void FBXUtility::normalsFromSmoothing(const Vector<Vector4>& positions, const Vector<int>& indices,
+		const Vector<int>& smoothing, Vector<Vector4>& normals)
+	{
+		std::vector<SmoothVertex> smoothNormals;
+		smoothNormals.resize(positions.size());
+
+		normals.resize(indices.size(), Vector4::ZERO);
+
+		UINT32 numPolygons = (UINT32)(indices.size() / 3);
+
+		int idx = 0;
+		for (UINT32 i = 0; i < numPolygons; i++)
+		{
+			for (UINT32 j = 0; j < 3; j++)
+			{
+				int prevOffset = (j > 0 ? j - 1 : 2);
+				int nextOffset = (j < 2 ? j + 1 : 0);
+
+				int current = indices[idx + j];
+
+				Vector3 v0 = (Vector3)positions[indices[idx + prevOffset]];
+				Vector3 v1 = (Vector3)positions[current];
+				Vector3 v2 = (Vector3)positions[indices[idx + nextOffset]];
+
+				Vector3 normal = Vector3::cross(v0 - v1, v2 - v1);
+				smoothNormals[current].addNormal(smoothing[idx + j], normal);
+
+				normals[idx + j] = normal;
+			}
+
+			idx += 3;
+		}
+
+		for (size_t i = 0; i < smoothNormals.size(); ++i)
+			smoothNormals[i].normalize();
+
+		idx = 0;
+		for (UINT32 i = 0; i < numPolygons; i++)
+		{
+			for (UINT32 j = 0; j < 3; j++)
+			{
+				if (smoothing[idx + i] != 0)
+				{
+					int current = indices[idx + i];
+
+					normals[idx + i] = (Vector4)smoothNormals[current].getNormal(smoothing[idx + i]);
+				}
+			}
+
+			idx += 3;
+		}
+	}
+
+	void FBXUtility::splitVertices(const FBXImportMesh& source, FBXImportMesh& dest)
+	{
+		dest.indices = source.indices;
+		dest.materials = source.materials;
+		dest.referencedBy = source.referencedBy;
+
+		dest.positions = source.positions;
+
+		// Make room for minimal set of vertices
+		UINT32 vertexCount = (UINT32)source.positions.size();
+		if (!source.normals.empty())
+			dest.normals.resize(vertexCount);
+
+		if (!source.tangents.empty())
+			dest.tangents.resize(vertexCount);
+
+		if (!source.bitangents.empty())
+			dest.bitangents.resize(vertexCount);
+
+		if (!source.colors.empty())
+			dest.colors.resize(vertexCount);
+
+		for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+		{
+			if (!source.UV[i].empty())
+				dest.UV[i].resize(vertexCount);
+		}
+
+		Vector<Vector<int>> splitsPerVertex;
+		splitsPerVertex.resize(source.positions.size());
+
+		Vector<int>& indices = dest.indices;
+		int indexCount = (int)dest.indices.size();
+		for (int i = 0; i < indexCount; i++)
+		{
+			int srcVertIdx = indices[i];
+			int dstVertIdx = -1;
+
+			// See if we already processed this vertex and if its attributes
+			// are close enough with the previous version
+			Vector<int>& splits = splitsPerVertex[srcVertIdx];
+			for (auto& splitVertIdx : splits)
+			{
+				if (!needsSplitAttributes(source, i, dest, splitVertIdx))
+					dstVertIdx = splitVertIdx;
+			}
+
+			// We didn't find a close-enough match
+			if (dstVertIdx == -1)
+			{
+				// First time we visited this vertex, so just copy over attributes
+				if (splits.empty())
+				{
+					dstVertIdx = srcVertIdx;
+					copyVertexAttributes(source, i, dest, srcVertIdx);
+				}
+				else // Split occurred, add a brand new vertex
+				{
+					dstVertIdx = (int)dest.positions.size();
+					addVertex(source, i, srcVertIdx, dest);
+				}
+
+				splits.push_back(dstVertIdx);
+			}
+
+			indices[i] = dstVertIdx;
+		}
+	}
+
+	void FBXUtility::flipWindingOrder(FBXImportMesh& input)
+	{
+		for (UINT32 i = 0; i < (UINT32)input.materials.size(); i += 3)
+		{
+			std::swap(input.materials[i + 1], input.materials[i + 2]);
+		}
+
+		for (UINT32 i = 0; i < (UINT32)input.indices.size(); i += 3)
+		{
+			std::swap(input.indices[i + 1], input.indices[i + 2]);
+		}
+	}
+
+	void FBXUtility::copyVertexAttributes(const FBXImportMesh& srcMesh, int srcIdx, FBXImportMesh& destMesh, int dstIdx)
+	{
+		if (!srcMesh.normals.empty())
+			destMesh.normals[dstIdx] = srcMesh.normals[srcIdx];
+
+		if (!srcMesh.tangents.empty())
+			destMesh.tangents[dstIdx] = srcMesh.tangents[srcIdx];
+
+		if (!srcMesh.bitangents.empty())
+			destMesh.bitangents[dstIdx] = srcMesh.bitangents[srcIdx];
+
+		if (!srcMesh.colors.empty())
+			destMesh.colors[dstIdx] = srcMesh.colors[srcIdx];
+
+		for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+		{
+			if (!srcMesh.UV[i].empty())
+				destMesh.UV[i][dstIdx] = srcMesh.UV[i][srcIdx];
+		}
+	}
+
+	void FBXUtility::addVertex(const FBXImportMesh& srcMesh, int srcIdx, int srcVertex, FBXImportMesh& destMesh)
+	{
+		destMesh.positions.push_back(srcMesh.positions[srcVertex]);
+
+		if (!srcMesh.normals.empty())
+			destMesh.normals.push_back(srcMesh.normals[srcIdx]);
+
+		if (!srcMesh.tangents.empty())
+			destMesh.tangents.push_back(srcMesh.tangents[srcIdx]);
+
+		if (!srcMesh.bitangents.empty())
+			destMesh.bitangents.push_back(srcMesh.bitangents[srcIdx]);
+
+		if (!srcMesh.colors.empty())
+			destMesh.colors.push_back(srcMesh.colors[srcIdx]);
+
+		for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+		{
+			if (!srcMesh.UV[i].empty())
+				destMesh.UV[i].push_back(srcMesh.UV[i][srcIdx]);
+		}
+	}
+
+	bool FBXUtility::needsSplitAttributes(const FBXImportMesh& meshA, int idxA, const FBXImportMesh& meshB, int idxB)
+	{
+		static const float SplitAngleCosine = Math::cos(Degree(1.0f));
+		static const float UVEpsilon = 0.001f;
+
+		if (!meshA.colors.empty())
+		{
+			if (meshA.colors[idxA] != meshB.colors[idxB])
+				return true;
+		}
+
+		if (!meshA.normals.empty())
+		{
+			float angleCosine = meshA.normals[idxA].dot(meshB.normals[idxB]);
+			if (angleCosine < SplitAngleCosine)
+				return true;
+		}
+
+		if (!meshA.tangents.empty())
+		{
+			float angleCosine = meshA.tangents[idxA].dot(meshB.tangents[idxB]);
+			if (angleCosine < SplitAngleCosine)
+				return true;
+		}
+
+		if (!meshA.bitangents.empty())
+		{
+			float angleCosine = meshA.bitangents[idxA].dot(meshB.bitangents[idxB]);
+			if (angleCosine < SplitAngleCosine)
+				return true;
+		}
+
+		for (UINT32 i = 0; i < FBX_IMPORT_MAX_UV_LAYERS; i++)
+		{
+			if (!meshA.UV[i].empty())
+			{
+				if (!Math::approxEquals(meshA.UV[i][idxA], meshB.UV[i][idxB], UVEpsilon))
+					return true;
+			}
+		}
+
+		return false;
+	}
+}

+ 8 - 0
BansheeUtility/Include/BsVector3.h

@@ -418,6 +418,14 @@ namespace BansheeEngine
 			vec2.normalize();
 		}
 
+        /**
+         * @brief	Calculates the dot (scalar) product of two vectors
+         */
+		static float dot(const Vector3& a, const Vector3& b)
+		{
+			return a.x * b.x + a.y * b.y + a.z * b.z;
+		}
+
 		/**
 		 * @brief	Normalizes the provided vector and returns a new normalized instance.
 		 */

+ 5 - 3
TODOExperimentation.txt

@@ -1,9 +1,11 @@
 -------------------------
 FBX
+Next:
+ - Test if import works using the next system
  - DefaultMeshData is defined in BansheeEngine but FBXImporter only includes Banshee Core
- - Transform tangent/bitangent and encode them into a 4-vector value
- - It should use DefaultMeshData
- - It should use and apply MeshImportOptions
+  - Therefore I am not using it in FBXImporter but I should
+
+Later:
  - Import skeleton
   - Unity just maps nodes referenced by FbxSkin clusters to bones, it do no separate parsing
  - Import keyframes