You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
925 lines
34 KiB
C++
925 lines
34 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
// Original file Copyright Crytek GMBH or its affiliates, used under license.
|
|
|
|
#include "FBXPlugin_precompiled.h"
|
|
|
|
|
|
// FBX SDK Help: http://download.autodesk.com/global/docs/fbxsdk2012/en_us/index.html
|
|
|
|
#include "FBXExporter.h"
|
|
#include "FBXSettingsDlg.h"
|
|
|
|
#include "IEditor.h"
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// constants used to convert cameras to and from Maya
|
|
|
|
static const float s_CONVERSION_BAKING_SAMPLE_RATE = 30.0f;
|
|
|
|
// IMPORT_PRE/POST_ROTATIONS determined by experimentation with fbx2015 in Maya and Max.
|
|
// We are transforming a Z-axis forward (Max/Maya) to a negative Y-axis forward camera (Editor).
|
|
// Analytically this shold involve a preRotation of -90 degrees around the x-axis.
|
|
// For Y-Up scenes, our Open 3D Engine Tools inserts an additional 180 rotation around the Y-axis when orienting .cgf files. We need to
|
|
// 'undo' for FBX anim curves. This does not apply to cameras which do not use .cgf files
|
|
static const FbxVector4 s_PRE_ROTATION_FOR_YUP_SCENES (-90.0f, .0f, .0f);
|
|
static const FbxVector4 s_POST_ROTATION_FOR_YUP_OBJECTS(-90.0f, 180.0f, .0f);
|
|
static const FbxVector4 s_POST_ROTATION_FOR_ZFORWARD_CAMERAS(-90.0f, .0f, .0f);
|
|
|
|
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace {
|
|
std::string GetFilePath(const std::string& filename)
|
|
{
|
|
int pos = filename.find_last_of("/\\");
|
|
if (pos > 0)
|
|
{
|
|
return filename.substr(0, pos + 1);
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
|
|
std::string GetFileName(const std::string& fullFilename)
|
|
{
|
|
int pos = fullFilename.find_last_of("/\\");
|
|
if (pos > 0)
|
|
{
|
|
return fullFilename.substr(pos + 1);
|
|
}
|
|
|
|
return fullFilename;
|
|
}
|
|
} // namespace
|
|
|
|
|
|
#ifdef DEBUG
|
|
///////////////////////////////////////////////////////////////////////
|
|
// debugging function to print all the keys in an Fbx Curve
|
|
void DebugPrintCurveKeys(FbxAnimCurve* pCurve, const QString& name)
|
|
{
|
|
if (pCurve)
|
|
{
|
|
ILog* log = GetIEditor()->GetSystem()->GetILog();
|
|
log->Log("\n%s", name);
|
|
for (int keyID = 0; keyID < pCurve->KeyGetCount(); ++keyID)
|
|
{
|
|
FbxAnimCurveKey key = pCurve->KeyGet(keyID);
|
|
|
|
log->Log("%.2g:%g, ", key.GetTime().GetSecondDouble(), key.GetValue());
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// CFBXExporter
|
|
CFBXExporter::CFBXExporter()
|
|
: m_pFBXManager(0)
|
|
, m_pFBXScene(0)
|
|
{
|
|
m_settings.bCopyTextures = true;
|
|
m_settings.bEmbedded = false;
|
|
m_settings.bAsciiFormat = false;
|
|
m_settings.bConvertAxesForMaxMaya = false;
|
|
}
|
|
|
|
|
|
const char* CFBXExporter::GetExtension() const
|
|
{
|
|
return "fbx";
|
|
}
|
|
|
|
|
|
const char* CFBXExporter::GetShortDescription() const
|
|
{
|
|
return "FBX format";
|
|
}
|
|
|
|
|
|
bool CFBXExporter::ExportToFile(const char* filename, const Export::IData* pData)
|
|
{
|
|
if (!m_pFBXManager)
|
|
{
|
|
m_pFBXManager = FbxManager::Create();
|
|
}
|
|
|
|
bool bAnimationExport = false;
|
|
|
|
for (int objectID = 0; objectID < pData->GetObjectCount(); ++objectID)
|
|
{
|
|
Export::Object* pObject = pData->GetObject(objectID);
|
|
|
|
if (pObject->GetEntityAnimationDataCount() > 0)
|
|
{
|
|
bAnimationExport = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// do nothing if the user cancels
|
|
if (!OpenFBXSettingsDlg(m_settings))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_path = GetFilePath(filename);
|
|
std::string name = GetFileName(filename);
|
|
|
|
// create an IOSettings object
|
|
FbxIOSettings* pSettings = FbxIOSettings::Create(m_pFBXManager, IOSROOT);
|
|
m_pFBXManager->SetIOSettings(pSettings);
|
|
|
|
// Create a scene object
|
|
FbxScene* pFBXScene = FbxScene::Create(m_pFBXManager, "Test");
|
|
pFBXScene->GetGlobalSettings().SetAxisSystem(FbxAxisSystem::Max);
|
|
pFBXScene->GetGlobalSettings().SetOriginalUpAxis(FbxAxisSystem::Max);
|
|
|
|
// Create document info
|
|
FbxDocumentInfo* pDocInfo = FbxDocumentInfo::Create(m_pFBXManager, "DocInfo");
|
|
pDocInfo->mTitle = name.c_str();
|
|
pDocInfo->mSubject = "Exported geometry from editor application.";
|
|
pDocInfo->mAuthor = "Editor FBX exporter.";
|
|
pDocInfo->mRevision = "rev. 1.0";
|
|
pDocInfo->mKeywords = "Editor FBX export";
|
|
pDocInfo->mComment = "";
|
|
pFBXScene->SetDocumentInfo(pDocInfo);
|
|
|
|
// Create nodes from objects and add them to the scene
|
|
FbxNode* pRootNode = pFBXScene->GetRootNode();
|
|
|
|
FbxAnimStack* pAnimStack = FbxAnimStack::Create(pFBXScene, "AnimStack");
|
|
FbxAnimLayer* pAnimBaseLayer = FbxAnimLayer::Create(pFBXScene, "AnimBaseLayer");
|
|
pAnimStack->AddMember(pAnimBaseLayer);
|
|
|
|
int numObjects = pData->GetObjectCount();
|
|
|
|
m_nodes.resize(0);
|
|
m_nodes.reserve(numObjects);
|
|
|
|
for (int i = 0; i < numObjects; ++i)
|
|
{
|
|
Export::Object* pObj = pData->GetObject(i);
|
|
FbxNode* newNode = NULL;
|
|
|
|
if (!bAnimationExport)
|
|
{
|
|
newNode = CreateFBXNode(pObj);
|
|
}
|
|
else
|
|
{
|
|
newNode = CreateFBXAnimNode(pFBXScene, pAnimBaseLayer, pObj);
|
|
}
|
|
|
|
m_nodes.push_back(newNode);
|
|
}
|
|
|
|
// solve hierarchy
|
|
for (int i = 0; i < m_nodes.size() && i < numObjects; ++i)
|
|
{
|
|
const Export::Object* pObj = pData->GetObject(i);
|
|
FbxNode* pNode = m_nodes[i];
|
|
FbxNode* pParentNode = 0;
|
|
if (pObj->nParent >= 0 && pObj->nParent < m_nodes.size())
|
|
{
|
|
pParentNode = m_nodes[pObj->nParent];
|
|
}
|
|
if (pParentNode)
|
|
{
|
|
pParentNode->AddChild(pNode);
|
|
}
|
|
else
|
|
{
|
|
pRootNode->AddChild(pNode);
|
|
}
|
|
}
|
|
|
|
int nFileFormat = -1;
|
|
|
|
if (m_settings.bAsciiFormat)
|
|
{
|
|
if (nFileFormat < 0 || nFileFormat >= m_pFBXManager->GetIOPluginRegistry()->GetWriterFormatCount())
|
|
{
|
|
nFileFormat = m_pFBXManager->GetIOPluginRegistry()->GetNativeWriterFormat();
|
|
int lFormatIndex, lFormatCount = m_pFBXManager->GetIOPluginRegistry()->GetWriterFormatCount();
|
|
for (lFormatIndex = 0; lFormatIndex < lFormatCount; lFormatIndex++)
|
|
{
|
|
if (m_pFBXManager->GetIOPluginRegistry()->WriterIsFBX(lFormatIndex))
|
|
{
|
|
FbxString lDesc = m_pFBXManager->GetIOPluginRegistry()->GetWriterFormatDescription(lFormatIndex);
|
|
const char* lASCII = "ascii";
|
|
if (lDesc.Find(lASCII) >= 0)
|
|
{
|
|
nFileFormat = lFormatIndex;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pSettings->SetBoolProp(EXP_FBX_EMBEDDED, m_settings.bEmbedded);
|
|
|
|
if (m_settings.bConvertAxesForMaxMaya)
|
|
{
|
|
// convert the scene from our Z-Up world to Maya's Y-Up World. This is stored in the FBX scene and both Max & Maya will
|
|
// import accordingly
|
|
FbxAxisSystem::MayaYUp.ConvertScene(pFBXScene);
|
|
|
|
// process all camera nodes in the scene to make them look down their negative Z-axis
|
|
convertCamerasForMaxMaya(pFBXScene->GetRootNode());
|
|
}
|
|
|
|
//Save a scene
|
|
bool bRet = false;
|
|
FbxExporter* pFBXExporter = FbxExporter::Create(m_pFBXManager, name.c_str());
|
|
|
|
// For backward compatilibity choose a widely compatible FBX version that's been out for a while:
|
|
if (pFBXExporter)
|
|
{
|
|
pFBXExporter->SetFileExportVersion(FBX_2014_00_COMPATIBLE);
|
|
|
|
if (pFBXExporter->Initialize(filename, nFileFormat, pSettings))
|
|
{
|
|
bRet = pFBXExporter->Export(pFBXScene);
|
|
}
|
|
pFBXExporter->Destroy();
|
|
}
|
|
|
|
pFBXScene->Destroy();
|
|
|
|
m_materials.clear();
|
|
m_meshMaterialIndices.clear();
|
|
|
|
m_pFBXManager->Destroy();
|
|
m_pFBXManager = 0;
|
|
|
|
if (bRet)
|
|
{
|
|
GetIEditor()->GetSystem()->GetILog()->Log("\nFBX Exporter: Exported successfully to %s.", name.c_str());
|
|
}
|
|
else
|
|
{
|
|
GetIEditor()->GetSystem()->GetILog()->LogError("\nFBX Exporter: Failed.");
|
|
}
|
|
|
|
return bRet;
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// This method finds all cameras (via recursion) in the node tree rooted at pNode and bakes the necessary
|
|
// transforms into any cameras it finds to take care of the conversion from Maya Z-forward to our negative-y forward cameras
|
|
void CFBXExporter::convertCamerasForMaxMaya(FbxNode* pNode) const
|
|
{
|
|
if (pNode)
|
|
{
|
|
// recurse to children to convert all cameras in the level
|
|
for (int i = 0; i < pNode->GetChildCount(); i++)
|
|
{
|
|
convertCamerasForMaxMaya(pNode->GetChild(i));
|
|
}
|
|
|
|
// process this node if it's a camera
|
|
convertCameraForMaxMaya(pNode);
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
// Maya cameras look down the negative z-axis. Ours look down the positive-y. This method bakes the necessary transforms
|
|
// into the local transforms of the given pNode if it's a camera to take care of the conversion
|
|
void CFBXExporter::convertCameraForMaxMaya(FbxNode* pNode) const
|
|
{
|
|
// Maya puts in an extra 90 x-rotation in the "Rotate Axis" channel. Adding an extra post-rotation fixes this,
|
|
// determined experimentally
|
|
static const FbxVector4 s_EXTRA_EXPORT_POST_ROTATION_FOR_MAX_MAYA(.0f, -90.0f, .0f);
|
|
|
|
if (pNode && pNode->GetNodeAttribute() && pNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eCamera)
|
|
{
|
|
// bake in s_PRE/POST_ROTATION_FOR_MAYA into anim curves
|
|
pNode->SetPivotState(FbxNode::eSourcePivot, FbxNode::ePivotActive);
|
|
pNode->SetPivotState(FbxNode::eDestinationPivot, FbxNode::ePivotActive);
|
|
|
|
pNode->SetPreRotation(FbxNode::eSourcePivot, s_PRE_ROTATION_FOR_YUP_SCENES);
|
|
pNode->SetPostRotation(FbxNode::eSourcePivot, s_POST_ROTATION_FOR_ZFORWARD_CAMERAS);
|
|
|
|
pNode->ResetPivotSetAndConvertAnimation(s_CONVERSION_BAKING_SAMPLE_RATE, true); // bake postRotation in for all animStacks
|
|
|
|
// Maya puts in an extra 90 x-rotation in the "Rotate Axis" channel. Adding an extra post-rotation after the baking of transforms
|
|
// fixes this - i.e. baked transformes change the camera's local rotation channels themselves, post-bake post-transforms get
|
|
// stuffed into Maya's 'Rotate Axis' channels
|
|
pNode->SetPostRotation(FbxNode::eSourcePivot, s_EXTRA_EXPORT_POST_ROTATION_FOR_MAX_MAYA);
|
|
}
|
|
}
|
|
|
|
FbxMesh* CFBXExporter::CreateFBXMesh(const Export::Object* pObj)
|
|
{
|
|
if (!m_pFBXManager)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int numVertices = pObj->GetVertexCount();
|
|
const Export::Vector3D* pVerts = pObj->GetVertexBuffer();
|
|
|
|
int numMeshes = pObj->GetMeshCount();
|
|
|
|
int numAllFaces = 0;
|
|
for (int j = 0; j < numMeshes; ++j)
|
|
{
|
|
const Export::Mesh* pMesh = pObj->GetMesh(j);
|
|
int numFaces = pMesh->GetFaceCount();
|
|
numAllFaces += numFaces;
|
|
}
|
|
|
|
if (numVertices == 0 || numAllFaces == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
FbxMesh* pFBXMesh = FbxMesh::Create(m_pFBXManager, pObj->name);
|
|
|
|
pFBXMesh->InitControlPoints(numVertices);
|
|
FbxVector4* pControlPoints = pFBXMesh->GetControlPoints();
|
|
|
|
for (int i = 0; i < numVertices; ++i)
|
|
{
|
|
const Export::Vector3D& vertex = pVerts[i];
|
|
pControlPoints[i] = FbxVector4(vertex.x, vertex.y, vertex.z);
|
|
}
|
|
|
|
/*
|
|
// We want to have one normal for each vertex (or control point),
|
|
// so we set the mapping mode to eBY_CONTROL_POINT.
|
|
FbxGeometryElementNormal* lElementNormal= pFBXMesh->CreateElementNormal();
|
|
|
|
//FbxVector4 lNormalZPos(0, 0, 1);
|
|
|
|
lElementNormal->SetMappingMode(FbxGeometryElement::eBY_CONTROL_POINT);
|
|
|
|
// Set the normal values for every control point.
|
|
lElementNormal->SetReferenceMode(FbxGeometryElement::eDIRECT);
|
|
|
|
lElementNormal->GetDirectArray().Add(lNormalZPos);
|
|
*/
|
|
|
|
|
|
int numTexCoords = pObj->GetTexCoordCount();
|
|
const Export::UV* pTextCoords = pObj->GetTexCoordBuffer();
|
|
if (numTexCoords)
|
|
{
|
|
// Create UV for Diffuse channel.
|
|
FbxGeometryElementUV* pFBXDiffuseUV = pFBXMesh->CreateElementUV("DiffuseUV");
|
|
FBX_ASSERT(pFBXDiffuseUV != NULL);
|
|
|
|
pFBXDiffuseUV->SetMappingMode(FbxGeometryElement::eByPolygonVertex);
|
|
pFBXDiffuseUV->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
|
|
|
|
for (int i = 0; i < numTexCoords; ++i)
|
|
{
|
|
const Export::UV& textCoord = pTextCoords[i];
|
|
pFBXDiffuseUV->GetDirectArray().Add(FbxVector2(textCoord.u, textCoord.v));
|
|
}
|
|
|
|
//Now we have set the UVs as eINDEX_TO_DIRECT reference and in eBY_POLYGON_VERTEX mapping mode
|
|
//we must update the size of the index array.
|
|
pFBXDiffuseUV->GetIndexArray().SetCount(numTexCoords);
|
|
}
|
|
|
|
pFBXMesh->ReservePolygonCount(numAllFaces);
|
|
pFBXMesh->ReservePolygonVertexCount(numAllFaces * 3);
|
|
|
|
// Set material mapping.
|
|
FbxGeometryElementMaterial* pMaterialElement = pFBXMesh->CreateElementMaterial();
|
|
pMaterialElement->SetMappingMode(FbxGeometryElement::eByPolygon);
|
|
pMaterialElement->SetReferenceMode(FbxGeometryElement::eIndexToDirect);
|
|
|
|
for (int j = 0; j < numMeshes; ++j)
|
|
{
|
|
const Export::Mesh* pMesh = pObj->GetMesh(j);
|
|
|
|
// Write all faces, convert the indices to one based indices
|
|
int numFaces = pMesh->GetFaceCount();
|
|
|
|
int polygonCount = pFBXMesh->GetPolygonCount();
|
|
pMaterialElement->GetIndexArray().SetCount(polygonCount + numFaces);
|
|
int materialIndex = m_meshMaterialIndices[pMesh];
|
|
|
|
const Export::Face* pFaces = pMesh->GetFaceBuffer();
|
|
for (int i = 0; i < numFaces; ++i)
|
|
{
|
|
const Export::Face& face = pFaces[i];
|
|
pFBXMesh->BeginPolygon(-1, -1, -1, false);
|
|
|
|
pFBXMesh->AddPolygon(face.idx[0], face.idx[0]);
|
|
pFBXMesh->AddPolygon(face.idx[1], face.idx[1]);
|
|
pFBXMesh->AddPolygon(face.idx[2], face.idx[2]);
|
|
|
|
//pFBXDiffuseUV->GetIndexArray().SetAt(face.idx[0], face.idx[0]);
|
|
|
|
pFBXMesh->EndPolygon();
|
|
//pPolygonGroupElement->GetIndexArray().Add(j);
|
|
|
|
pMaterialElement->GetIndexArray().SetAt(polygonCount + i, materialIndex);
|
|
}
|
|
}
|
|
|
|
return pFBXMesh;
|
|
}
|
|
|
|
|
|
FbxFileTexture* CFBXExporter::CreateFBXTexture(const char* pTypeName, const char* pName)
|
|
{
|
|
std::string filename = pName;
|
|
|
|
if (m_settings.bCopyTextures)
|
|
{
|
|
// check if file exists
|
|
QFileInfo fi(pName);
|
|
if (!fi.exists() || fi.isDir())
|
|
{
|
|
GetIEditor()->GetSystem()->GetILog()->LogError("\nFBX Exporter: Texture %s is not on the disk.", pName);
|
|
}
|
|
else
|
|
{
|
|
filename = m_path;
|
|
filename += GetFileName(pName);
|
|
if (QFile::copy(pName, filename.c_str()))
|
|
{
|
|
GetIEditor()->GetSystem()->GetILog()->Log("\nFBX Exporter: Texture %s was copied.", pName);
|
|
}
|
|
filename = GetFileName(pName); // It works for Maya, but not good for MAX
|
|
}
|
|
}
|
|
|
|
FbxFileTexture* pFBXTexture = FbxFileTexture::Create(m_pFBXManager, pTypeName);
|
|
pFBXTexture->SetFileName(filename.c_str());
|
|
pFBXTexture->SetTextureUse(FbxTexture::eStandard);
|
|
pFBXTexture->SetMappingType(FbxTexture::eUV);
|
|
pFBXTexture->SetMaterialUse(FbxFileTexture::eModelMaterial);
|
|
pFBXTexture->SetSwapUV(false);
|
|
pFBXTexture->SetTranslation(0.0, 0.0);
|
|
pFBXTexture->SetScale(1.0, 1.0);
|
|
pFBXTexture->SetRotation(0.0, 0.0);
|
|
|
|
return pFBXTexture;
|
|
}
|
|
|
|
|
|
FbxSurfaceMaterial* CFBXExporter::CreateFBXMaterial(const std::string& name, const Export::Mesh* pMesh)
|
|
{
|
|
auto it = m_materials.find(name);
|
|
if (it != m_materials.end())
|
|
{
|
|
return it->second;
|
|
}
|
|
|
|
FbxSurfacePhong* pMaterial = FbxSurfacePhong::Create(m_pFBXManager, name.c_str());
|
|
|
|
pMaterial->Diffuse.Set(FbxDouble3(pMesh->material.diffuse.r, pMesh->material.diffuse.g, pMesh->material.diffuse.b));
|
|
pMaterial->DiffuseFactor.Set(1.0);
|
|
pMaterial->Specular.Set(FbxDouble3(pMesh->material.specular.r, pMesh->material.specular.g, pMesh->material.specular.b));
|
|
pMaterial->SpecularFactor.Set(1.0);
|
|
|
|
// Opacity
|
|
pMaterial->TransparentColor.Set(FbxDouble3(1.0f, 1.0f, 1.0f)); // need explicitly setup
|
|
pMaterial->TransparencyFactor.Set(1.0f - pMesh->material.opacity);
|
|
|
|
// Glossiness
|
|
// CRC TODO: Why was this commented out?
|
|
//pMaterial->Shininess.Set(pow(2, pMesh->material.shininess * 10));
|
|
|
|
if (pMesh->material.mapDiffuse[0] != '\0')
|
|
{
|
|
FbxFileTexture* pFBXTexture = CreateFBXTexture("Diffuse", pMesh->material.mapDiffuse);
|
|
pMaterial->Diffuse.ConnectSrcObject(pFBXTexture);
|
|
}
|
|
|
|
if (pMesh->material.mapSpecular[0] != '\0')
|
|
{
|
|
FbxFileTexture* pFBXTexture = CreateFBXTexture("Specular", pMesh->material.mapSpecular);
|
|
pMaterial->Specular.ConnectSrcObject(pFBXTexture);
|
|
}
|
|
|
|
if (pMesh->material.mapOpacity[0] != '\0')
|
|
{
|
|
FbxFileTexture* pFBXTexture = CreateFBXTexture("Opacity", pMesh->material.mapOpacity);
|
|
pMaterial->TransparentColor.ConnectSrcObject(pFBXTexture);
|
|
}
|
|
// CRC TODO: Why was this commented out?
|
|
/*
|
|
if (pMesh->material.mapBump[0] != '\0')
|
|
{
|
|
FbxFileTexture* pFBXTexture = CreateFBXTexture("Bump", pMesh->material.mapBump);
|
|
pMaterial->Bump.ConnectSrcObject(pFBXTexture);
|
|
}
|
|
*/
|
|
|
|
if (pMesh->material.mapDisplacement[0] != '\0')
|
|
{
|
|
FbxFileTexture* pFBXTexture = CreateFBXTexture("Displacement", pMesh->material.mapDisplacement);
|
|
pMaterial->DisplacementColor.ConnectSrcObject(pFBXTexture);
|
|
}
|
|
|
|
m_materials[name] = pMaterial;
|
|
|
|
return pMaterial;
|
|
}
|
|
|
|
|
|
FbxNode* CFBXExporter::CreateFBXAnimNode(FbxScene* pScene, FbxAnimLayer* pAnimBaseLayer, const Export::Object* pObj)
|
|
{
|
|
FbxNode* pAnimNode = FbxNode::Create(pScene, pObj->name);
|
|
pAnimNode->LclTranslation.GetCurveNode(pAnimBaseLayer, true);
|
|
pAnimNode->LclRotation.GetCurveNode(pAnimBaseLayer, true);
|
|
pAnimNode->LclScaling.GetCurveNode(pAnimBaseLayer, true);
|
|
|
|
FbxCamera* pCamera = FbxCamera::Create(pScene, pObj->name);
|
|
|
|
if (pObj->entityType == Export::eCamera)
|
|
{
|
|
pCamera->SetApertureMode(FbxCamera::eHorizontal);
|
|
pCamera->SetFormat(FbxCamera::eCustomFormat);
|
|
pCamera->SetAspect(FbxCamera::eFixedRatio, 1.777778f, 1.0);
|
|
|
|
pAnimNode->SetNodeAttribute(pCamera);
|
|
|
|
if (pObj->cameraTargetNodeName[0])
|
|
{
|
|
for (int i = 0; i < m_nodes.size(); ++i)
|
|
{
|
|
FbxNode* pCameraTargetNode = m_nodes[i];
|
|
const char* nodeName = pCameraTargetNode->GetName();
|
|
if (strcmp(nodeName, pObj->cameraTargetNodeName) == 0)
|
|
{
|
|
FbxMarker* pMarker = FbxMarker::Create(pScene, "");
|
|
pCameraTargetNode->SetNodeAttribute(pMarker);
|
|
pAnimNode->SetTarget(pCameraTargetNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pObj->GetEntityAnimationDataCount() > 0)
|
|
{
|
|
int animDataCount = pObj->GetEntityAnimationDataCount();
|
|
|
|
for (int animDataIndex = 0; animDataIndex < animDataCount; ++animDataIndex)
|
|
{
|
|
const Export::EntityAnimData* pAnimData = pObj->GetEntityAnimationData(animDataIndex);
|
|
|
|
FbxTime time = 0;
|
|
time.SetSecondDouble(pAnimData->keyTime);
|
|
|
|
FbxAnimCurve* pCurve = NULL;
|
|
|
|
switch (pAnimData->dataType)
|
|
{
|
|
case Export::AnimParamType::PositionX:
|
|
pCurve = pAnimNode->LclTranslation.GetCurve(pAnimBaseLayer, "X", true);
|
|
break;
|
|
case Export::AnimParamType::PositionY:
|
|
pCurve = pAnimNode->LclTranslation.GetCurve(pAnimBaseLayer, "Y", true);
|
|
break;
|
|
case Export::AnimParamType::PositionZ:
|
|
pCurve = pAnimNode->LclTranslation.GetCurve(pAnimBaseLayer, "Z", true);
|
|
break;
|
|
case Export::AnimParamType::RotationX:
|
|
pCurve = pAnimNode->LclRotation.GetCurve(pAnimBaseLayer, "X", true);
|
|
break;
|
|
case Export::AnimParamType::RotationY:
|
|
pCurve = pAnimNode->LclRotation.GetCurve(pAnimBaseLayer, "Y", true);
|
|
break;
|
|
case Export::AnimParamType::RotationZ:
|
|
pCurve = pAnimNode->LclRotation.GetCurve(pAnimBaseLayer, "Z", true);
|
|
break;
|
|
case Export::AnimParamType::FOV:
|
|
{
|
|
pCurve = pCamera->FieldOfView.GetCurve(pAnimBaseLayer, "FieldOfView", true);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (pCurve)
|
|
{
|
|
int keyIndex = 0;
|
|
|
|
pCurve->KeyModifyBegin();
|
|
keyIndex = pCurve->KeyInsert(time);
|
|
pCurve->KeySet(keyIndex, time, pAnimData->keyValue, FbxAnimCurveDef::eInterpolationCubic, FbxAnimCurveDef::eTangentBreak, pAnimData->rightTangent, pAnimData->leftTangent, FbxAnimCurveDef::eWeightedAll, pAnimData->rightTangentWeight, pAnimData->leftTangentWeight);
|
|
|
|
pCurve->KeySetLeftDerivative(keyIndex, pAnimData->leftTangent);
|
|
pCurve->KeySetRightDerivative(keyIndex, pAnimData->rightTangent);
|
|
|
|
pCurve->KeySetLeftTangentWeight(keyIndex, pAnimData->leftTangentWeight);
|
|
pCurve->KeySetRightTangentWeight(keyIndex, pAnimData->rightTangentWeight);
|
|
|
|
FbxAnimCurveTangentInfo keyLeftInfo;
|
|
keyLeftInfo.mAuto = 0;
|
|
keyLeftInfo.mDerivative = pAnimData->leftTangent;
|
|
keyLeftInfo.mWeight = pAnimData->leftTangentWeight;
|
|
keyLeftInfo.mWeighted = true;
|
|
keyLeftInfo.mVelocity = 0.0f;
|
|
keyLeftInfo.mHasVelocity = false;
|
|
|
|
FbxAnimCurveTangentInfo keyRightInfo;
|
|
keyRightInfo.mAuto = 0;
|
|
keyRightInfo.mDerivative = pAnimData->rightTangent;
|
|
keyRightInfo.mWeight = pAnimData->rightTangentWeight;
|
|
keyRightInfo.mWeighted = true;
|
|
keyRightInfo.mVelocity = 0.0f;
|
|
keyRightInfo.mHasVelocity = false;
|
|
|
|
pCurve->KeySetLeftDerivativeInfo(keyIndex, keyLeftInfo);
|
|
pCurve->KeySetRightDerivativeInfo(keyIndex, keyRightInfo);
|
|
|
|
pCurve->KeyModifyEnd();
|
|
}
|
|
}
|
|
}
|
|
|
|
return pAnimNode;
|
|
}
|
|
|
|
FbxNode* CFBXExporter::CreateFBXNode(const Export::Object* pObj)
|
|
{
|
|
if (!m_pFBXManager)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// create a FbxNode
|
|
FbxNode* pNode = FbxNode::Create(m_pFBXManager, pObj->name);
|
|
pNode->LclTranslation.Set(FbxVector4(pObj->pos.x, pObj->pos.y, pObj->pos.z));
|
|
|
|
// Rotation: Create Euler angels from quaternion throughout a matrix
|
|
FbxAMatrix rotMat;
|
|
rotMat.SetQ(FbxQuaternion(pObj->rot.v.x, pObj->rot.v.y, pObj->rot.v.z, pObj->rot.w));
|
|
pNode->LclRotation.Set(rotMat.GetR());
|
|
|
|
pNode->LclScaling.Set(FbxVector4(pObj->scale.x, pObj->scale.y, pObj->scale.z));
|
|
|
|
|
|
// collect materials
|
|
int materialIndex = 0;
|
|
if (pObj->GetMeshCount() != 0 && pObj->materialName[0] != '\0')
|
|
{
|
|
for (int i = 0; i < pObj->GetMeshCount(); ++i)
|
|
{
|
|
const Export::Mesh* pMesh = pObj->GetMesh(i);
|
|
|
|
if (pMesh->material.name[0] == '\0')
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// find if material was created for this object
|
|
int findIndex = -1;
|
|
for (int j = 0; j < i; ++j)
|
|
{
|
|
const Export::Mesh* pTestMesh = pObj->GetMesh(j);
|
|
if (!strcmp(pTestMesh->material.name, pMesh->material.name))
|
|
{
|
|
findIndex = m_meshMaterialIndices[pTestMesh];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (findIndex != -1)
|
|
{
|
|
m_meshMaterialIndices[pMesh] = findIndex;
|
|
continue;
|
|
}
|
|
|
|
std::string materialName = pObj->materialName;
|
|
if (strcmp(pObj->materialName, pMesh->material.name))
|
|
{
|
|
materialName += ':';
|
|
materialName += pMesh->material.name;
|
|
}
|
|
|
|
FbxSurfaceMaterial* pFBXMaterial = CreateFBXMaterial(materialName, pMesh);
|
|
if (pFBXMaterial)
|
|
{
|
|
pNode->AddMaterial(pFBXMaterial);
|
|
m_meshMaterialIndices[pMesh] = materialIndex++;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
FbxMesh* pFBXMesh = CreateFBXMesh(pObj);
|
|
if (pFBXMesh)
|
|
{
|
|
pNode->SetNodeAttribute(pFBXMesh);
|
|
pNode->SetShadingMode(FbxNode::eTextureShading);
|
|
}
|
|
|
|
return pNode;
|
|
}
|
|
|
|
void CFBXExporter::Release()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
inline f32 Maya2SandboxFOVDeg(const f32 fov, const f32 ratio) { return RAD2DEG(2.0f * atanf(tan(DEG2RAD(fov) / 2.0f) / ratio)); };
|
|
|
|
void CFBXExporter::FillAnimationData(Export::Object* pObject, FbxNode* pNode, [[maybe_unused]] FbxAnimLayer* pAnimLayer, FbxAnimCurve* pCurve, Export::AnimParamType paramType)
|
|
{
|
|
if (pCurve)
|
|
{
|
|
for (int keyID = 0; keyID < pCurve->KeyGetCount(); ++keyID)
|
|
{
|
|
Export::EntityAnimData entityData;
|
|
|
|
FbxAnimCurveKey key = pCurve->KeyGet(keyID);
|
|
entityData.keyValue = key.GetValue();
|
|
FbxTime time = key.GetTime();
|
|
entityData.keyTime = aznumeric_cast<float>(time.GetSecondDouble());
|
|
|
|
entityData.leftTangent = pCurve->KeyGetLeftDerivative(keyID);
|
|
entityData.rightTangent = pCurve->KeyGetRightDerivative(keyID);
|
|
entityData.leftTangentWeight = pCurve->KeyGetLeftTangentWeight(keyID);
|
|
entityData.rightTangentWeight = pCurve->KeyGetRightTangentWeight(keyID);
|
|
|
|
if (paramType == Export::AnimParamType::FocalLength && pNode && pNode->GetCamera())
|
|
{
|
|
// special handling for Focal Length - we convert it to FoV for use in-engine (including switching the paramType)
|
|
// We handle this because Maya 2015 doesn't save Angle of View or Field of View animation in FBX - it only uses FocalLength.
|
|
entityData.dataType = Export::AnimParamType::FOV;
|
|
// Open 3D Engine field of view is the vertical angle
|
|
pNode->GetCamera()->SetApertureMode(FbxCamera::eVertical);
|
|
entityData.keyValue = aznumeric_cast<float>(pNode->GetCamera()->ComputeFieldOfView(entityData.keyValue));
|
|
}
|
|
else
|
|
{
|
|
entityData.dataType = paramType;
|
|
}
|
|
|
|
// If the Exporter is Sandbox or Maya, convert fov, positions
|
|
QString exporterName = m_pFBXScene->GetDocumentInfo()->Original_ApplicationName.Get().Buffer();
|
|
|
|
if (!exporterName.toLower().contains("3ds"))
|
|
{
|
|
if (paramType == Export::AnimParamType::PositionX || paramType == Export::AnimParamType::PositionY || paramType == Export::AnimParamType::PositionZ)
|
|
{
|
|
entityData.rightTangent /= 100.0f;
|
|
entityData.leftTangent /= 100.0f;
|
|
entityData.keyValue /= 100.0f;
|
|
}
|
|
else if (paramType == Export::AnimParamType::FOV) // Maya 2015 uses FocalLength instead of FoV - assuming this is for legacy Sandbox or Maya?
|
|
{
|
|
const float kAspectRatio = 1.777778f;
|
|
entityData.keyValue = Maya2SandboxFOVDeg(entityData.keyValue, kAspectRatio);
|
|
}
|
|
}
|
|
|
|
pObject->SetEntityAnimationData(entityData);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool CFBXExporter::ImportFromFile(const char* filename, Export::IData* pData)
|
|
{
|
|
FbxManager* pFBXManager = FbxManager::Create();
|
|
|
|
FbxScene* pFBXScene = FbxScene::Create(pFBXManager, "Test");
|
|
m_pFBXScene = pFBXScene;
|
|
pFBXScene->GetGlobalSettings().SetAxisSystem(FbxAxisSystem::Max);
|
|
pFBXScene->GetGlobalSettings().SetOriginalUpAxis(FbxAxisSystem::Max);
|
|
|
|
FbxImporter* pImporter = FbxImporter::Create(pFBXManager, "");
|
|
|
|
FbxIOSettings* pSettings = FbxIOSettings::Create(pFBXManager, IOSROOT);
|
|
|
|
pFBXManager->SetIOSettings(pSettings);
|
|
pSettings->SetBoolProp(IMP_FBX_ANIMATION, true);
|
|
|
|
const bool lImportStatus = pImporter->Initialize(filename, -1, pFBXManager->GetIOSettings());
|
|
if (!lImportStatus)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool bLoadStatus = pImporter->Import(pFBXScene);
|
|
|
|
if (!bLoadStatus)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// record the original axis system used in the import file and then convert the file to Open 3D Engine's coord system,
|
|
// which matches Max's (Z-Up, negative Y-forward cameras)
|
|
int upSign = 1;
|
|
FbxAxisSystem importFileAxisSystem = pFBXScene->GetGlobalSettings().GetAxisSystem();
|
|
FbxAxisSystem::EUpVector importSceneUpVector = importFileAxisSystem.GetUpVector(upSign);
|
|
FbxAxisSystem::Max.ConvertScene(pFBXScene);
|
|
|
|
QString exporterName = pFBXScene->GetDocumentInfo()->Original_ApplicationName.Get().Buffer();
|
|
// const bool fromMaya = (exporterName.contains("maya", Qt::CaseInsensitive));
|
|
// const bool fromMax = (exporterName.contains("max", Qt::CaseInsensitive));
|
|
|
|
FbxNode* pRootNode = pFBXScene->GetRootNode();
|
|
|
|
if (pRootNode)
|
|
{
|
|
for (int animStackID = 0; animStackID < pFBXScene->GetSrcObjectCount<FbxAnimStack>(); ++animStackID)
|
|
{
|
|
FbxAnimStack* pAnimStack = pFBXScene->GetSrcObject<FbxAnimStack>(animStackID);
|
|
const int animLayersCount = pAnimStack->GetMemberCount<FbxAnimLayer>();
|
|
|
|
for (int layerID = 0; layerID < animLayersCount; ++layerID)
|
|
{
|
|
FbxAnimLayer* pAnimLayer = pAnimStack->GetMember<FbxAnimLayer>(layerID);
|
|
|
|
const int nodeCount = pRootNode->GetChildCount();
|
|
|
|
for (int nodeID = 0; nodeID < nodeCount; ++nodeID)
|
|
{
|
|
FbxNode* pNode = pRootNode->GetChild(nodeID);
|
|
|
|
if (pNode)
|
|
{
|
|
FbxAnimCurve* pCurve = 0;
|
|
Export::Object* pObject = pData->AddObject(pNode->GetName());
|
|
|
|
if (pObject)
|
|
{
|
|
azstrncpy(pObject->name, sizeof(pObject->name), pNode->GetName(), sizeof(pObject->name) - 1);
|
|
pObject->name[sizeof(pObject->name) - 1] = '\0';
|
|
|
|
FbxCamera* pCamera = pNode->GetCamera();
|
|
|
|
// convert animation for Y-Up scenes and for Z-forward Cameras
|
|
if (importSceneUpVector == FbxAxisSystem::eYAxis || pCamera)
|
|
{
|
|
pNode->SetPivotState(FbxNode::eSourcePivot, FbxNode::ePivotActive);
|
|
pNode->SetPivotState(FbxNode::eDestinationPivot, FbxNode::ePivotActive);
|
|
|
|
if (importSceneUpVector == FbxAxisSystem::eYAxis)
|
|
{
|
|
// Maps RY to -RZ and RZ to RY
|
|
pNode->SetPreRotation(FbxNode::eSourcePivot, s_PRE_ROTATION_FOR_YUP_SCENES);
|
|
}
|
|
|
|
if (pCamera)
|
|
{
|
|
// Converts Y-Up, -Z-forward cameras to Open 3D Engine Z-Up, Y-forward cameras
|
|
// It is needed regardless of the scene up vector
|
|
pNode->SetPostRotation(FbxNode::eSourcePivot, s_POST_ROTATION_FOR_ZFORWARD_CAMERAS);
|
|
}
|
|
else
|
|
{
|
|
// Objects from a Y-Up scene (i.e. not cameras). 'undo' the extra transform that the Open 3D Engine Tool
|
|
// bakes in to .cgf files from YUp scenes.
|
|
pNode->SetPostRotation(FbxNode::eSourcePivot, s_POST_ROTATION_FOR_YUP_OBJECTS);
|
|
}
|
|
|
|
// bake the pre/post rotations into the anim curves
|
|
pNode->ConvertPivotAnimationRecursive(pAnimStack, FbxNode::eSourcePivot, s_CONVERSION_BAKING_SAMPLE_RATE);
|
|
}
|
|
|
|
if (pCamera)
|
|
{
|
|
// extract specialized channels for cameras
|
|
pCurve = pCamera->FocalLength.GetCurve(pAnimLayer, "FocalLength");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::FocalLength);
|
|
pCurve = pCamera->FieldOfView.GetCurve(pAnimLayer, "FieldOfView", true);
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::FOV);
|
|
}
|
|
|
|
pCurve = pNode->LclTranslation.GetCurve(pAnimLayer, "X");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::PositionX);
|
|
pCurve = pNode->LclTranslation.GetCurve(pAnimLayer, "Y");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::PositionY);
|
|
pCurve = pNode->LclTranslation.GetCurve(pAnimLayer, "Z");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::PositionZ);
|
|
pCurve = pNode->LclRotation.GetCurve(pAnimLayer, "X");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::RotationX);
|
|
pCurve = pNode->LclRotation.GetCurve(pAnimLayer, "Y");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::RotationY);
|
|
pCurve = pNode->LclRotation.GetCurve(pAnimLayer, "Z");
|
|
FillAnimationData(pObject, pNode, pAnimLayer, pCurve, Export::AnimParamType::RotationZ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|