986 lines
39 KiB
C++
986 lines
39 KiB
C++
/* -----------------------------------------------------------------------------
|
|
GSFramework
|
|
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
|
----------------------------------------------------------------------------- */
|
|
|
|
|
|
#include "import_fbx/import_fbx.h"
|
|
#include "scene3d/scene.h"
|
|
#include "scene3d/mobject.h"
|
|
#include "scene3d/mlight.h"
|
|
#include "scene3d/mcamera.h"
|
|
#include "core/graphic_resource_factory.h"
|
|
#include "core/geometry.h"
|
|
#include "core/renderer.h"
|
|
#include "metafile/nml_object.h"
|
|
#include "picture/pict_io.h"
|
|
#include "filesystem/filesystem.h"
|
|
#include "platform.h"
|
|
|
|
using namespace GS;
|
|
using namespace GS::Core;
|
|
using namespace GS::S3D;
|
|
|
|
|
|
//------------------------------------------------------------------------------
|
|
static FbxAMatrix ConvertGlobalMatrix(const FbxAMatrix &m) {
|
|
FbxAMatrix k_m;
|
|
k_m.SetS(FbxVector4(-1, 1, 1));
|
|
return m * k_m;
|
|
}
|
|
|
|
static Matrix4 FBXMatrixToMatrix4(const FbxAMatrix &fbx_m) {
|
|
Matrix4 matrix;
|
|
for (int i = 0; i < 4; ++i)
|
|
for (int j = 0; j < 4; ++j)
|
|
matrix.m[i][j] = (float) fbx_m[j][i];
|
|
|
|
return matrix;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
FbxScene *FBXImporter::LoadNativeScene(const char *fbx_path) {
|
|
// Create an IOSettings object
|
|
FbxIOSettings *ios = FbxIOSettings::Create(sdk_manager, IOSROOT);
|
|
|
|
// set some IOSettings options
|
|
ios->SetBoolProp(IMP_FBX_MATERIAL, true);
|
|
ios->SetBoolProp(IMP_FBX_TEXTURE, true);
|
|
ios->SetBoolProp(IMP_FBX_LINK, false);
|
|
ios->SetBoolProp(IMP_FBX_SHAPE, false);
|
|
ios->SetBoolProp(IMP_FBX_GOBO, false);
|
|
ios->SetBoolProp(IMP_FBX_ANIMATION, true);
|
|
ios->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true);
|
|
|
|
// Create an empty scene
|
|
FbxScene *fbx_scene = FbxScene::Create(sdk_manager, "");
|
|
|
|
// Create an importer.
|
|
FBXImporter *fbx_importer = FBXImporter::Create(sdk_manager, "");
|
|
|
|
if (fbx_importer->Initialize(fbx_path, -1, ios) && fbx_importer->Import(fbx_scene)) {
|
|
input_path = String(fbx_path).CutFileName();
|
|
|
|
// Convert to our axis system and scale.
|
|
FbxAxisSystem axis_system(FbxAxisSystem::eYAxis, FbxAxisSystem::eParityOdd, FbxAxisSystem::eRightHanded);
|
|
axis_system.ConvertScene(fbx_scene);
|
|
|
|
const FbxSystemUnit::ConversionOptions options =
|
|
{
|
|
false, /* mConvertRrsNodes */
|
|
true, /* mConvertAllLimits */
|
|
true, /* mConvertClusters */
|
|
true, /* mConvertLightIntensity */
|
|
true, /* mConvertPhotometricLProperties */
|
|
true /* mConvertCameraClipPlanes */
|
|
};
|
|
FbxSystemUnit unit_system(100.f / config->scale);
|
|
unit_system.ConvertScene(fbx_scene, options);
|
|
} else {
|
|
fbx_scene->Destroy();
|
|
fbx_scene = NULL;
|
|
}
|
|
|
|
fbx_importer->Destroy();
|
|
return fbx_scene;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//#define __DEBUG_EULER__
|
|
|
|
//------------------------------------------------------------------------------
|
|
void FBXImporter::ExportMotionChannel(FbxNode *pNode, FbxAnimCurve *pCurve, Motion *motion,
|
|
MotionChannel::Type channel_type) {
|
|
if (!pCurve)
|
|
return;
|
|
|
|
MotionChannel *channel = motion->AddChannel(channel_type);
|
|
if (!channel)
|
|
return;
|
|
|
|
channel->AllocatePoint(pCurve->KeyGetCount());
|
|
|
|
for (int n = 0; n < pCurve->KeyGetCount(); ++n) {
|
|
FbxTime time = pCurve->KeyGetTime(n);
|
|
|
|
CurvePoint *point = (CurvePoint *) channel->GetPoints()[n];
|
|
point->t = Time::fromSec(float(time.GetSecondDouble()));
|
|
point->v = pCurve->KeyGetValue(n);
|
|
|
|
switch (pCurve->KeyGetInterpolation(n)) {
|
|
default:
|
|
case FbxAnimCurveDef::eInterpolationLinear: point->shape = CurvePoint::Shape_Linear;
|
|
break;
|
|
case FbxAnimCurveDef::eInterpolationConstant: point->shape = CurvePoint::Shape_Step;
|
|
break;
|
|
case FbxAnimCurveDef::eInterpolationCubic: point->shape = CurvePoint::Shape_Hermite;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBXImporter::BakeTransformation(FbxNode *pNode, MItem *item, Motion *motion) {
|
|
motion->SetUseQuaternion(true);
|
|
|
|
// Allocate position/scale.
|
|
#ifdef __DEBUG_EULER__
|
|
motion->AddChannels(9);
|
|
#else
|
|
motion->AddChannels(6);
|
|
#endif
|
|
|
|
motion->GetChannel(0)->type = MotionChannel::XPos;
|
|
motion->GetChannel(1)->type = MotionChannel::YPos;
|
|
motion->GetChannel(2)->type = MotionChannel::ZPos;
|
|
motion->GetChannel(3)->type = MotionChannel::XScl;
|
|
motion->GetChannel(4)->type = MotionChannel::YScl;
|
|
motion->GetChannel(5)->type = MotionChannel::ZScl;
|
|
|
|
#ifdef __DEBUG_EULER__
|
|
motion->GetChannel(6)->type = MotionChannel::XRot;
|
|
motion->GetChannel(7)->type = MotionChannel::YRot;
|
|
motion->GetChannel(8)->type = MotionChannel::ZRot;
|
|
#endif
|
|
|
|
// Bake animation.
|
|
FbxTime tStart = fbx_scene->GetEvaluator()->GetContext()->ReferenceStart.Get(),
|
|
tEnd = fbx_scene->GetEvaluator()->GetContext()->ReferenceStop.Get();
|
|
|
|
FbxTime tStep;
|
|
tStep.SetSecondDouble(1.0 / double(config->frame_per_second));
|
|
|
|
for (FbxTime t = tStart; t < (tEnd + tStep); t += tStep) // Make sure to include the last key.
|
|
{
|
|
Time ts = Time::fromSec(float(t.GetSecondDouble()));
|
|
|
|
Vector4 p, s;
|
|
Matrix3 r;
|
|
FbxAMatrix m;
|
|
|
|
int dummy = -1;
|
|
FbxAMatrix node_global_transform = fbx_scene->GetEvaluator()->GetNodeGlobalTransformFast(pNode, dummy, t);
|
|
dummy = -1;
|
|
|
|
if (pNode->GetParent()) {
|
|
FbxAMatrix parent_global_transform = fbx_scene->GetEvaluator()->GetNodeGlobalTransformFast(
|
|
pNode->GetParent(), dummy, t);
|
|
dummy = -1;
|
|
m = ConvertGlobalMatrix(parent_global_transform).Inverse() * ConvertGlobalMatrix(node_global_transform);
|
|
} else
|
|
m = ConvertGlobalMatrix(node_global_transform);
|
|
|
|
FBXMatrixToMatrix4(m).Decompose(&p, &s, &r);
|
|
|
|
motion->GetChannel(0)->Append(CurvePoint(ts, p.x));
|
|
motion->GetChannel(1)->Append(CurvePoint(ts, p.y));
|
|
motion->GetChannel(2)->Append(CurvePoint(ts, p.z));
|
|
motion->GetChannel(3)->Append(CurvePoint(ts, s.x));
|
|
motion->GetChannel(4)->Append(CurvePoint(ts, s.y));
|
|
motion->GetChannel(5)->Append(CurvePoint(ts, s.z));
|
|
|
|
#ifdef __DEBUG_EULER__
|
|
Vector4 e = r.AsEuler();
|
|
motion->GetChannel(6)->Insert(CurvePoint(ts, e.x));
|
|
motion->GetChannel(7)->Insert(CurvePoint(ts, e.y));
|
|
motion->GetChannel(8)->Insert(CurvePoint(ts, e.z));
|
|
#else
|
|
Quaternion q = Quaternion::FromMatrix3(r);
|
|
motion->GetQuaternion().Insert(QuaternionKey(ts, q));
|
|
#endif
|
|
}
|
|
// motion->Optimize();
|
|
}
|
|
|
|
void FBXImporter::ExportMotions(FbxNode *pNode, MItem *item) {
|
|
if (config->import_animation == false)
|
|
return;
|
|
|
|
for (int n = 0; n < fbx_scene->GetSrcObjectCount<FbxAnimStack>(); n++) {
|
|
FbxAnimStack *anim_stack = FbxCast<FbxAnimStack>(fbx_scene->GetSrcObject<FbxAnimStack>(n));
|
|
if (!anim_stack)
|
|
continue;
|
|
fbx_scene->GetEvaluator()->SetContext(anim_stack);
|
|
|
|
// Convert to motion.
|
|
Motion *motion = new Motion;
|
|
if (!motion)
|
|
continue;
|
|
|
|
String take_name(anim_stack->GetNameOnly());
|
|
|
|
motion->name = take_name;
|
|
BakeTransformation(pNode, item, motion);
|
|
|
|
// Add to scene motion set.
|
|
{
|
|
SceneMotion *set = NULL;
|
|
ListForeachPtr(SceneMotion *, s, scene->motion.motions)
|
|
if (s->name == motion->name) {
|
|
set = s;
|
|
break;
|
|
}
|
|
|
|
if (set == NULL) // create a new motion set
|
|
{
|
|
set = new SceneMotion;
|
|
|
|
set->name = take_name;
|
|
scene->motion.motions.Add(set);
|
|
}
|
|
|
|
SceneMotion::ItemMotion *item_motion = new SceneMotion::ItemMotion; // new item motion
|
|
item_motion->uid = item->GetUid();
|
|
item_motion->motion = motion;
|
|
|
|
set->item_motions.Add(item_motion); // add to set
|
|
}
|
|
|
|
// Add to item motion list.
|
|
{
|
|
// item->automation_player->AddMotion(motion);
|
|
}
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool FBXImporter::ExportDeformers(FbxMesh *fbx_mesh, FbxNode *pNode, Geometry &geo, MObject *object) {
|
|
FbxSkin *fbx_skin = ((FbxSkin *) fbx_mesh->GetDeformer(0, FbxDeformer::eSkin));
|
|
if (!fbx_skin)
|
|
return false;
|
|
|
|
// Allocate geometry skin.
|
|
geo.skin.Allocate(geo.vtx.GetCount());
|
|
|
|
for (uint n = 0; n < geo.vtx.GetCount(); ++n)
|
|
for (int j = 0; j < __PV_BONE_LIMIT__; ++j) {
|
|
geo.skin[n].bone_index[j] = 0;
|
|
geo.skin[n].w[j] = 0.f;
|
|
}
|
|
|
|
// For each skin entry select the clusters with the largest weight.
|
|
geo.AllocateBone(fbx_skin->GetClusterCount());
|
|
|
|
for (int n = 0; n < (int) geo.bone_name.GetCount(); ++n) {
|
|
FbxCluster *cluster = fbx_skin->GetCluster(n);
|
|
if (FbxNode *bone = cluster->GetLink())
|
|
geo.bone_name[n] = bone->GetName();
|
|
|
|
// Import bind pose.
|
|
FbxAMatrix cluster_matrix, bind_matrix;
|
|
cluster->GetTransformMatrix(cluster_matrix);
|
|
cluster->GetTransformLinkMatrix(bind_matrix);
|
|
|
|
geo.bone_bind_matrix[n] = FBXMatrixToMatrix4(
|
|
(ConvertGlobalMatrix(cluster_matrix).Inverse() * ConvertGlobalMatrix(bind_matrix)).Inverse());
|
|
|
|
// Import weights.
|
|
int *fbx_index = cluster->GetControlPointIndices();
|
|
double *fbx_weight = cluster->GetControlPointWeights();
|
|
|
|
for (int i = 0; i < cluster->GetControlPointIndicesCount(); ++i) {
|
|
GeometrySkin *skin = &geo.skin[fbx_index[i]];
|
|
|
|
// Perform insertion.
|
|
for (int c = 0; c < __PV_BONE_LIMIT__; ++c)
|
|
if (fbx_weight[i] > skin->w[c]) {
|
|
// Shift the lower influences out.
|
|
for (int j = __PV_BONE_LIMIT__ - 1; j > c; --j) {
|
|
skin->w[j] = skin->w[j - 1];
|
|
skin->bone_index[j] = skin->bone_index[j - 1];
|
|
}
|
|
|
|
// Insert new influence.
|
|
skin->w[c] = (float) fbx_weight[i];
|
|
skin->bone_index[c] = (ushort) n;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Normalize weights.
|
|
for (uint n = 0; n < geo.vtx.GetCount(); ++n) {
|
|
GeometrySkin *skin = &geo.skin[n];
|
|
|
|
float w_sum = 0;
|
|
for (int c = 0; c < __PV_BONE_LIMIT__; ++c)
|
|
w_sum += skin->w[c];
|
|
if (w_sum > 0)
|
|
for (int c = 0; c < __PV_BONE_LIMIT__; ++c)
|
|
skin->w[c] /= w_sum;
|
|
}
|
|
|
|
// Set geometry and bind bones.
|
|
object->geometry = geo.name;
|
|
if (object->AllocateSkin(geo.GetBoneCount()))
|
|
for (uint n = 0; n < geo.GetBoneCount(); ++n)
|
|
if (MItem *item = ExportNode(fbx_skin->GetCluster(n)->GetLink()))
|
|
object->BindBone(n, item->GetBaseItem());
|
|
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String FBXImporter::ExportFileTexture(FbxFileTexture *fbx_texture) {
|
|
if (!fbx_texture)
|
|
return NULL;
|
|
|
|
// Try to locate texture.
|
|
String in_path;
|
|
|
|
forever {
|
|
in_path = fbx_texture->GetFileName();
|
|
if (Platform::Get().io->Exists(in_path))
|
|
break;
|
|
|
|
in_path.FileCutPath();
|
|
if (Platform::Get().io->Exists(in_path))
|
|
break;
|
|
|
|
in_path = input_path + "/" + in_path;
|
|
if (Platform::Get().io->Exists(in_path))
|
|
break;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
// Import texture.
|
|
String out_path;
|
|
if (GetOutputPath(out_path, config->base_path, in_path.GetFileName(), "texture", in_path.GetFileExtension(),
|
|
config->exists_policy_texture)) {
|
|
Platform::Get().io->FileCopy(in_path, out_path);
|
|
out_path = Platform::Get().io->StripRootPath(out_path);
|
|
}
|
|
return out_path;
|
|
}
|
|
|
|
String FBXImporter::ExportLayeredTexture(FbxLayeredTexture *object) {
|
|
String out_path;
|
|
|
|
for (int n = 0; n < object->GetSrcObjectCount<FbxTexture>(); ++n) {
|
|
if (FbxFileTexture *t = object->GetSrcObject<FbxFileTexture>(n))
|
|
out_path = ExportFileTexture(t);
|
|
// if (FbxLayeredTexture *t = object->GetSrcObject(FBX_TYPE(FbxLayeredTexture), n))
|
|
// texture = ExportLayeredTexture(t);
|
|
|
|
if (!out_path.IsEmpty())
|
|
break;
|
|
}
|
|
return out_path;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String FBXImporter::SaveMaterial(const Material &material, const char *name) {
|
|
String out_path;
|
|
if (GetOutputPath(out_path, config->base_path, name, "material", "nmm", config->exists_policy_material))
|
|
NML::SaveToFile(material, out_path);
|
|
|
|
return Platform::Get().io->StripRootPath(out_path);
|
|
}
|
|
|
|
String FBXImporter::ExportMaterial(FbxSurfaceMaterial *fbx_material, FbxMesh *fbx_mesh, bool use_skin) {
|
|
static const char *texture_type_to_export[] =
|
|
{
|
|
FbxSurfaceMaterial::sDiffuse,
|
|
FbxSurfaceMaterial::sEmissive,
|
|
FbxSurfaceMaterial::sAmbient,
|
|
FbxSurfaceMaterial::sSpecular,
|
|
FbxSurfaceMaterial::sNormalMap,
|
|
FbxSurfaceMaterial::sShininess,
|
|
FbxSurfaceMaterial::sBump,
|
|
FbxSurfaceMaterial::sTransparentColor,
|
|
FbxSurfaceMaterial::sReflection,
|
|
0
|
|
};
|
|
|
|
static MaterialChannel export_texture_to_channel[] =
|
|
{
|
|
Channel_Diffuse,
|
|
Channel_SelfIllum,
|
|
Channel_Light,
|
|
Channel_Specular,
|
|
Channel_Normal,
|
|
Channel_Glossiness,
|
|
Channel_Normal,
|
|
Channel_Opacity,
|
|
Channel_Reflection
|
|
};
|
|
|
|
if (!fbx_material)
|
|
return NULL;
|
|
|
|
Material material;
|
|
material.renderword |= Material::Render_Smooth;
|
|
if (use_skin)
|
|
material.renderword |= Material::Render_Skinned;
|
|
|
|
// Phong.
|
|
if (fbx_material->GetClassId().Is(FbxSurfacePhong::ClassId)) {
|
|
FbxSurfacePhong *fbx_phong = (FbxSurfacePhong *) fbx_material;
|
|
material.specular.Set(float(fbx_phong->Specular.Get()[0] * fbx_phong->SpecularFactor.Get()),
|
|
float(fbx_phong->Specular.Get()[1] * fbx_phong->SpecularFactor.Get()),
|
|
float(fbx_phong->Specular.Get()[2] * fbx_phong->SpecularFactor.Get()));
|
|
material.glossiness = Types::Clamp((float) fbx_phong->Shininess.Get() / 64.f, 0.01f, 0.5f);
|
|
// Completely random conversion factor.
|
|
}
|
|
|
|
// Lambert.
|
|
if (fbx_material->GetClassId().Is(FbxSurfacePhong::ClassId) || fbx_material->GetClassId().Is(
|
|
FbxSurfaceLambert::ClassId)) {
|
|
FbxSurfaceLambert *fbx_lambert = (FbxSurfaceLambert *) fbx_material;
|
|
material.ambient.Set(float(fbx_lambert->Ambient.Get()[0] * fbx_lambert->AmbientFactor.Get()),
|
|
float(fbx_lambert->Ambient.Get()[1] * fbx_lambert->AmbientFactor.Get()),
|
|
float(fbx_lambert->Ambient.Get()[2] * fbx_lambert->AmbientFactor.Get()));
|
|
material.diffuse.Set(float(fbx_lambert->Diffuse.Get()[0] * fbx_lambert->DiffuseFactor.Get()),
|
|
float(fbx_lambert->Diffuse.Get()[1] * fbx_lambert->DiffuseFactor.Get()),
|
|
float(fbx_lambert->Diffuse.Get()[2] * fbx_lambert->DiffuseFactor.Get()));
|
|
material.self.Set(float(fbx_lambert->Emissive.Get()[0] * fbx_lambert->EmissiveFactor.Get()),
|
|
float(fbx_lambert->Emissive.Get()[1] * fbx_lambert->EmissiveFactor.Get()),
|
|
float(fbx_lambert->Emissive.Get()[2] * fbx_lambert->EmissiveFactor.Get()));
|
|
// material.opacity = 1.f - fbx_lambert->GetTransparencyFactor().Get(); // Broken exporters make the importer appear broken.
|
|
}
|
|
|
|
// Export material textures.
|
|
for (int t = 0; texture_type_to_export[t]; ++t) {
|
|
// Export texture from FBX.
|
|
FbxProperty fbx_texture_prop = fbx_material->FindProperty(texture_type_to_export[t]);
|
|
|
|
FbxTexture *fbx_texture = fbx_texture_prop.GetSrcObject<FbxTexture>(0);
|
|
if (!fbx_texture)
|
|
continue;
|
|
|
|
String texture;
|
|
if (FbxFileTexture *t = fbx_texture_prop.GetSrcObject<FbxFileTexture>(0))
|
|
texture = ExportFileTexture(t);
|
|
if (FbxLayeredTexture *t = fbx_texture_prop.GetSrcObject<FbxLayeredTexture>(0))
|
|
texture = ExportLayeredTexture(t);
|
|
|
|
// Identify UV channel.
|
|
int uv_index = -1, uv_count = 0;
|
|
for (int l = 0; l < fbx_mesh->GetLayerCount(); ++l) {
|
|
FbxLayer *fbx_layer = fbx_mesh->GetLayer(l);
|
|
|
|
for (int n = 0; n < fbx_layer->GetUVSetCount(); ++n) {
|
|
FbxArray<FbxLayerElement::EType> uv_types = fbx_layer->GetUVSetChannels();
|
|
for (int t = 0; t < uv_types.GetCount(); ++t)
|
|
if (fbx_texture->UVSet.Get() == fbx_layer->GetUVs(uv_types[t])->GetName()) {
|
|
uv_index = uv_count;
|
|
goto done_uv;
|
|
}
|
|
|
|
++uv_count;
|
|
if (uv_count == __UV_PER_GEOMETRY__)
|
|
goto done_uv;
|
|
}
|
|
}
|
|
|
|
done_uv:;
|
|
|
|
// Create stage.
|
|
if (Material::TextureStage *stage = material.NewStage(export_texture_to_channel[t], texture, Material::UV_UV,
|
|
(uchar) (uv_index == -1 ? 0 : uv_index))) {
|
|
// Normal map defaults to tangent.
|
|
if (stage->channel == Channel_Normal)
|
|
material.renderword |= Material::Render_NormalTangent;
|
|
}
|
|
}
|
|
return SaveMaterial(material, fbx_material->GetName());
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
String FBXImporter::ExportGeometry(FbxMesh *fbx_mesh, FbxNode *pNode, MObject *object) {
|
|
// Build local transformation.
|
|
FbxAMatrix mesh_matrix, mesh_rmatrix;
|
|
|
|
if (pNode) {
|
|
mesh_matrix.SetTRS(pNode->GetGeometricTranslation(FbxNode::eSourcePivot),
|
|
pNode->GetGeometricRotation(FbxNode::eSourcePivot),
|
|
pNode->GetGeometricScaling(FbxNode::eSourcePivot));
|
|
mesh_rmatrix.SetR(pNode->GetGeometricRotation(FbxNode::eSourcePivot));
|
|
|
|
FbxAMatrix export_global_mtx;
|
|
export_global_mtx.SetS(FbxVector4(-1, 1, 1));
|
|
|
|
mesh_matrix = export_global_mtx * mesh_matrix;
|
|
mesh_rmatrix = export_global_mtx * mesh_rmatrix;
|
|
}
|
|
|
|
// Export.
|
|
Geometry geo;
|
|
geo.name = pNode->GetName();
|
|
|
|
// Transfer topology.
|
|
geo.AllocateVertex(fbx_mesh->GetControlPointsCount());
|
|
for (uint n = 0; n < geo.vtx.GetCount(); ++n) {
|
|
FbxVector4 v = mesh_matrix.MultT(fbx_mesh->GetControlPoints()[n]);
|
|
geo.vtx[n].Set((float) v[0], (float) v[1], (float) v[2]);
|
|
}
|
|
|
|
geo.AllocatePolygon(fbx_mesh->GetPolygonCount());
|
|
for (uint n = 0; n < geo.pol.GetCount(); ++n) {
|
|
geo.pol[n].vtx_count = (ushort) fbx_mesh->GetPolygonSize(n);
|
|
geo.pol[n].material = 0;
|
|
}
|
|
|
|
Array<uint> pol_index;
|
|
geo.ComputePolygonIndex(pol_index);
|
|
geo.AllocatePolygonBinding();
|
|
|
|
#define __PolIndex (pol_index[p] + v)
|
|
#define __PolRemapIndex (pol_index[p] + (geo.pol[p].vtx_count - 1 - v))
|
|
// #define __PolRemapIndex (geometry->pol_index[p] + v)
|
|
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v)
|
|
geo.pol[p].binding[v] = fbx_mesh->GetPolygonVertices()[__PolRemapIndex];
|
|
|
|
// Export materials.
|
|
FbxLayer *fbx_layer = fbx_mesh->GetLayer(0);
|
|
|
|
// Normal.
|
|
if (const FbxLayerElementNormal *normal_layer = fbx_layer->GetNormals())
|
|
if (geo.vtx_normal.Allocate(geo.binding.GetCount()))
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
FbxVector4 N;
|
|
fbx_mesh->GetPolygonVertexNormal(p, v, N);
|
|
N = mesh_rmatrix.MultT(N);
|
|
geo.vtx_normal[__PolRemapIndex].Set((float) N[0], (float) N[1], (float) N[2]);
|
|
}
|
|
|
|
// Tangent and binormal.
|
|
const FbxLayerElementTangent *tangent_layer = fbx_layer->GetTangents();
|
|
const FbxLayerElementBinormal *binormal_layer = fbx_layer->GetBinormals();
|
|
|
|
if (tangent_layer && binormal_layer) {
|
|
if ((tangent_layer->GetMappingMode() == FbxLayerElement::eByPolygonVertex) && (
|
|
binormal_layer->GetMappingMode() == FbxLayerElement::eByPolygonVertex)) {
|
|
if (geo.vtx_tangent.Allocate(geo.binding.GetCount()))
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
FbxVector4 T = tangent_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? tangent_layer->GetDirectArray()[tangent_layer->GetIndexArray()[
|
|
__PolRemapIndex]]
|
|
: tangent_layer->GetDirectArray()[__PolRemapIndex];
|
|
T = mesh_rmatrix.MultT(T);
|
|
geo.vtx_tangent[__PolIndex].T.Set((float) T[0], (float) -T[1], (float) T[2]);
|
|
// This is UV dependent and textures are reversed on V from the FBX convention.
|
|
|
|
FbxVector4 B = binormal_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? binormal_layer->GetDirectArray()[binormal_layer->GetIndexArray()[
|
|
__PolRemapIndex]]
|
|
: binormal_layer->GetDirectArray()[__PolRemapIndex];
|
|
B = mesh_rmatrix.MultT(T);
|
|
geo.vtx_tangent[__PolIndex].B.Set((float) B[0], (float) -B[1], (float) B[2]);
|
|
// This is UV dependent and textures are reversed on V from the FBX convention.
|
|
}
|
|
} else
|
|
__LOG_W__ << "Unsupported tangent layer mapping mode (" << tangent_layer->GetMappingMode() << ").\n";
|
|
}
|
|
|
|
// Vertex color.
|
|
if (const FbxLayerElementVertexColor *color_layer = fbx_layer->GetVertexColors()) {
|
|
if (geo.rgb.Allocate(geo.binding.GetCount()))
|
|
switch (color_layer->GetMappingMode()) {
|
|
case FbxLayerElement::eByControlPoint:
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
uint v_idx = geo.pol[p].binding[v];
|
|
const FbxColor &cl = color_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? color_layer->GetDirectArray()[color_layer->GetIndexArray()[
|
|
v_idx]]
|
|
: color_layer->GetDirectArray()[v_idx];
|
|
geo.rgb[__PolIndex].Set((float) cl.mRed, (float) cl.mGreen, (float) cl.mBlue);
|
|
}
|
|
break;
|
|
|
|
case FbxLayerElement::eByPolygonVertex:
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
const FbxColor &cl = color_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? color_layer->GetDirectArray()[color_layer->GetIndexArray()[
|
|
__PolRemapIndex]]
|
|
: color_layer->GetDirectArray()[__PolRemapIndex];
|
|
geo.rgb[__PolIndex].Set((float) cl.mRed, (float) cl.mGreen, (float) cl.mBlue);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
__LOG_W__ << "Unsupported vertex color layer mapping mode (" << color_layer->GetMappingMode() <<
|
|
").\n";
|
|
}
|
|
else
|
|
__LOG_E__ << "Failed to allocate vertex color set.\n";
|
|
}
|
|
|
|
// UV Channel (searched for on all available layers).
|
|
uint uv_count = 0;
|
|
for (int l = 0; l < fbx_mesh->GetLayerCount(); ++l) {
|
|
FbxLayer *fbx_layer = fbx_mesh->GetLayer(l);
|
|
|
|
for (int n = 0; n < fbx_layer->GetUVSetCount(); ++n) {
|
|
const FbxLayerElementUV *uv_layer = fbx_layer->GetUVSets()[n];
|
|
|
|
if (geo.uv[uv_count].Allocate(geo.binding.GetCount()))
|
|
switch (uv_layer->GetMappingMode()) {
|
|
case FbxLayerElement::eByControlPoint:
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
uint v_idx = geo.pol[p].binding[v];
|
|
const FbxVector2 &UV = uv_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? uv_layer->GetDirectArray()[uv_layer->GetIndexArray()[
|
|
v_idx]]
|
|
: uv_layer->GetDirectArray()[v_idx];
|
|
geo.uv[uv_count][__PolIndex].Set((float) UV[0], 1.f - (float) UV[1]);
|
|
}
|
|
break;
|
|
|
|
case FbxLayerElement::eByPolygonVertex:
|
|
for (uint p = 0; p < geo.pol.GetCount(); ++p)
|
|
for (int v = 0; v < geo.pol[p].vtx_count; ++v) {
|
|
const FbxVector2 &UV = uv_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect
|
|
? uv_layer->GetDirectArray()[uv_layer->GetIndexArray()[
|
|
__PolRemapIndex]]
|
|
: uv_layer->GetDirectArray()[__PolRemapIndex];
|
|
geo.uv[uv_count][__PolIndex].Set((float) UV[0], 1.f - (float) UV[1]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
__LOG_W__ << "Unsupported UV layer mapping mode (" << uv_layer->GetMappingMode() << ").\n";
|
|
}
|
|
else
|
|
__LOG_E__ << "Failed to allocate UV set.\n";
|
|
|
|
if (++uv_count == __UV_PER_GEOMETRY__) {
|
|
__LOG_W__ << "UV map limit per geometry exceeded (" << __UV_PER_GEOMETRY__ <<
|
|
"), increase nUVMapLimit.\n";
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (uv_count == __UV_PER_GEOMETRY__)
|
|
break;
|
|
}
|
|
|
|
// Export deformers.
|
|
bool use_skin = ExportDeformers(fbx_mesh, pNode, geo, object);
|
|
|
|
// Materials.
|
|
int material_count = fbx_mesh->GetNode()->GetMaterialCount();
|
|
|
|
if (material_count > 0) {
|
|
geo.material_table.Allocate(material_count);
|
|
for (int n = 0; n < material_count; ++n)
|
|
geo.material_table[n].name = ExportMaterial((FbxSurfaceMaterial *) fbx_mesh->GetNode()->GetMaterial(n),
|
|
fbx_mesh, use_skin);
|
|
} else {
|
|
Material material;
|
|
if (use_skin)
|
|
material.renderword |= Material::Render_Skinned;
|
|
|
|
geo.material_table.Allocate(1);
|
|
geo.material_table[0].name = SaveMaterial(material, geo.name);
|
|
}
|
|
|
|
// Export the material mapping to polygon.
|
|
const FbxLayerElementMaterial *material_layer = fbx_layer->GetMaterials();
|
|
|
|
if (material_layer)
|
|
switch (material_layer->GetMappingMode()) {
|
|
case FbxLayerElement::eByPolygon: {
|
|
// Map polygon to material.
|
|
if (material_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect)
|
|
for (uint n = 0; n < geo.pol.GetCount(); ++n) {
|
|
int idx = material_layer->GetIndexArray().GetAt(n);
|
|
geo.pol[n].material = (ushort) idx;
|
|
if (geo.pol[n].material >= geo.material_table.GetCount()) {
|
|
__LOG_E__ << "Invalid material index (" << idx << ") for polygon " << n <<
|
|
" (FBX powered).\n";
|
|
geo.pol[n].material = 0;
|
|
}
|
|
}
|
|
else
|
|
for (uint n = 0; n < geo.pol.GetCount(); ++n)
|
|
geo.pol[n].material = (ushort) n;
|
|
}
|
|
break;
|
|
|
|
case FbxLayerElement::eAllSame: {
|
|
if (material_layer->GetReferenceMode() == FbxLayerElement::eIndexToDirect)
|
|
for (uint n = 0; n < geo.pol.GetCount(); ++n) {
|
|
int idx = material_layer->GetIndexArray().GetAt(0);
|
|
geo.pol[n].material = (ushort) idx;
|
|
if (geo.pol[n].material >= geo.material_table.GetCount()) {
|
|
__LOG_E__ << "Invalid material index (" << idx << ") for polygon " << n <<
|
|
" (FBX powered).\n";
|
|
geo.pol[n].material = 0;
|
|
}
|
|
}
|
|
else
|
|
for (uint n = 0; n < geo.pol.GetCount(); ++n)
|
|
geo.pol[n].material = 0;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
__LOG_W__ << "Unsupported material mapping mode (" << material_layer->GetMappingMode() << ").\n";
|
|
break;
|
|
}
|
|
|
|
// Output to path.
|
|
String out_path;
|
|
if (GetOutputPath(out_path, config->base_path, geo.name, "geometry", "nmg", config->exists_policy_geometry)) {
|
|
geo.name = out_path;
|
|
NML::SaveToFile(geo, geo.name);
|
|
geo.name = Platform::Get().io->StripRootPath(geo.name);
|
|
}
|
|
|
|
object->geometry = geo.name;
|
|
return geo.name;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
MObject *FBXImporter::ExportObject(FbxNodeAttribute *pAttr, FbxNode *pNode) {
|
|
FbxMesh *fbx_mesh = (FbxMesh *) pAttr;
|
|
MObject *object = new MObject;
|
|
object->name = pNode->GetNameOnly();
|
|
scene->AddItem(object, true);
|
|
ExportGeometry(fbx_mesh, pNode, object);
|
|
return object;
|
|
}
|
|
|
|
MItem *FBXImporter::ExportCamera(FbxNodeAttribute *pAttr, FbxNode *pNode) {
|
|
FbxCamera *fbx_camera = (FbxCamera *) pAttr;
|
|
MCamera *camera = new MCamera;
|
|
camera->name = pNode->GetNameOnly();
|
|
scene->AddItem(camera, true);
|
|
|
|
if (fbx_camera->GetNearPlane() != 10)
|
|
camera->SetNearClippingPlane((float) fbx_camera->GetNearPlane());
|
|
if (fbx_camera->GetFarPlane() != 4000)
|
|
camera->SetFarClippingPlane((float) fbx_camera->GetFarPlane());
|
|
|
|
camera->aspect_ratio = (float) fbx_camera->GetPixelRatio();
|
|
camera->SetFov(Units::DegreeToRadian((float) fbx_camera->FieldOfView.Get()));
|
|
camera->is_orthographic = asbool(fbx_camera->ProjectionType.Get() == FbxCamera::eOrthogonal);
|
|
|
|
return camera;
|
|
}
|
|
|
|
MItem *FBXImporter::ExportLight(FbxNodeAttribute *pAttr, FbxNode *pNode) {
|
|
FbxLight *fbx_light = (FbxLight *) pAttr;
|
|
MLight *light = new MLight;
|
|
light->name = pNode->GetNameOnly();
|
|
scene->AddItem(light, true);
|
|
|
|
switch (fbx_light->LightType.Get()) {
|
|
case FbxLight::ePoint: light->model = MLight::Model_Point;
|
|
break;
|
|
case FbxLight::eDirectional: light->model = MLight::Model_Linear;
|
|
break;
|
|
case FbxLight::eSpot: light->model = MLight::Model_Spot;
|
|
break;
|
|
}
|
|
|
|
light->diffuse_color.Set((float) fbx_light->Color.Get()[0], (float) fbx_light->Color.Get()[1],
|
|
(float) fbx_light->Color.Get()[2]);
|
|
light->diffuse_intensity = (float) fbx_light->Intensity.Get() / 100.f;
|
|
light->specular_color = light->diffuse_color;
|
|
|
|
if (fbx_light->EnableFarAttenuation.Get()) {
|
|
light->range = (float) fbx_light->FarAttenuationEnd.Get();
|
|
light->volume_range = light->range + Units::Mtr(0.5f);
|
|
}
|
|
|
|
if (fbx_light->CastShadows.Get())
|
|
light->shadow = Light::Shadow_Map;
|
|
light->shadow_color.Set((float) fbx_light->ShadowColor.Get()[0], (float) fbx_light->ShadowColor.Get()[1],
|
|
(float) fbx_light->ShadowColor.Get()[2]);
|
|
return light;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool FBXImporter::GetNodeItem(FbxNode *pNode, MItem **item) {
|
|
ListForeachPtr(ExportedNode *, exported_node, node_list)
|
|
if (exported_node->node == pNode) {
|
|
if (item)
|
|
*item = exported_node->item;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
MItem *FBXImporter::ExportNode(FbxNode *pNode) {
|
|
if (config->event_handler)
|
|
config->event_handler->LoadProgress(String::Format("Importing node '%s'...", pNode->GetName()),
|
|
(float) current_node_index / fbx_scene->GetNodeCount());
|
|
current_node_index++;
|
|
|
|
MItem *item = NULL;
|
|
if (GetNodeItem(pNode, &item))
|
|
return item;
|
|
|
|
// Export this node.
|
|
if (pNode != fbx_scene->GetRootNode()) {
|
|
if (pNode->GetNodeAttribute()) {
|
|
FbxNodeAttribute::EType type = pNode->GetNodeAttribute()->GetAttributeType();
|
|
|
|
switch (type) {
|
|
default:
|
|
case FbxNodeAttribute::eUnknown:
|
|
case FbxNodeAttribute::eNull:
|
|
case FbxNodeAttribute::eMarker:
|
|
case FbxNodeAttribute::eNurbs:
|
|
case FbxNodeAttribute::ePatch:
|
|
case FbxNodeAttribute::eCameraStereo:
|
|
case FbxNodeAttribute::eCameraSwitcher:
|
|
case FbxNodeAttribute::eOpticalReference:
|
|
case FbxNodeAttribute::eOpticalMarker:
|
|
case FbxNodeAttribute::eNurbsCurve:
|
|
case FbxNodeAttribute::eTrimNurbsSurface:
|
|
case FbxNodeAttribute::eBoundary:
|
|
case FbxNodeAttribute::eNurbsSurface:
|
|
case FbxNodeAttribute::eShape:
|
|
case FbxNodeAttribute::eLODGroup:
|
|
case FbxNodeAttribute::eSubDiv:
|
|
case FbxNodeAttribute::eSkeleton:
|
|
if (MObject *o = new MObject) {
|
|
o->name = pNode->GetNameOnly();
|
|
scene->AddItem(o, true);
|
|
item = o;
|
|
}
|
|
break;
|
|
|
|
case FbxNodeAttribute::eMesh:
|
|
item = ExportObject(pNode->GetNodeAttribute(), pNode);
|
|
break;
|
|
case FbxNodeAttribute::eCamera:
|
|
item = ExportCamera(pNode->GetNodeAttribute(), pNode);
|
|
break;
|
|
case FbxNodeAttribute::eLight:
|
|
item = ExportLight(pNode->GetNodeAttribute(), pNode);
|
|
break;
|
|
}
|
|
} else {
|
|
MObject *o = new MObject;
|
|
o->name = pNode->GetNameOnly();
|
|
scene->AddItem(o, true);
|
|
item = o;
|
|
}
|
|
}
|
|
|
|
// Register node.
|
|
node_list.Add(new ExportedNode(pNode, item));
|
|
|
|
if (item) {
|
|
FbxAMatrix m;
|
|
if (pNode->GetParent())
|
|
m = ConvertGlobalMatrix(fbx_scene->GetEvaluator()->GetNodeGlobalTransform(pNode->GetParent())).Inverse() *
|
|
ConvertGlobalMatrix(fbx_scene->GetEvaluator()->GetNodeGlobalTransform(pNode));
|
|
else m = ConvertGlobalMatrix(fbx_scene->GetEvaluator()->GetNodeGlobalTransform(pNode));
|
|
|
|
item->GetBaseItem()->SetMatrix(FBXMatrixToMatrix4(m));
|
|
ExportMotions(pNode, item);
|
|
}
|
|
|
|
// Export children.
|
|
for (int i = 0; i < pNode->GetChildCount(); i++) {
|
|
MItem *child = ExportNode(pNode->GetChild(i));
|
|
if (child && item)
|
|
child->GetBaseItem()->SetParent(item->GetBaseItem());
|
|
}
|
|
return item;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
bool FBXImporter::TestImport(const char *uri) {
|
|
String ext = String::FileGetExtension(uri).Lower();
|
|
return asbool((ext == "dae") || (ext == "fbx") || (ext == "3ds"));
|
|
}
|
|
|
|
bool FBXImporter::ImportScene(Scene *_scene, const char *_input, const Config &_config, Group **) {
|
|
if (!sdk_manager)
|
|
__ERR__(__LOG_E__ << "Importer not initialized.\n", false)
|
|
if (_input == NULL)
|
|
__ERR__(__LOG_E__ << "No input file specified.\n", false)
|
|
if (_scene == NULL)
|
|
__ERR__(__LOG_E__ << "No scene to load into.\n", false)
|
|
|
|
// Load native FBX.
|
|
scene = _scene;
|
|
config = &_config;
|
|
if ((fbx_scene = LoadNativeScene(_input)) == NULL)
|
|
return false;
|
|
|
|
if (config->event_handler)
|
|
config->event_handler->OpenLoad();
|
|
|
|
// Drop lists.
|
|
geometry_list.Clear();
|
|
|
|
// Perform conversion.
|
|
current_node_index = 0;
|
|
ExportNode(fbx_scene->GetRootNode());
|
|
|
|
// Convert globals.
|
|
FbxGlobalLightSettings &gsettings = fbx_scene->GlobalLightSettings();
|
|
|
|
scene->ambient_color.Set((float) gsettings.GetAmbientColor().mRed, (float) gsettings.GetAmbientColor().mGreen,
|
|
(float) gsettings.GetAmbientColor().mBlue);
|
|
scene->ambient_intensity = 1.f;
|
|
|
|
scene->fog_color.Set((float) gsettings.GetFogColor().mRed, (float) gsettings.GetFogColor().mGreen,
|
|
(float) gsettings.GetFogColor().mBlue);
|
|
if (gsettings.GetFogEnable()) {
|
|
scene->fog_near = (float) gsettings.GetFogStart();
|
|
scene->fog_far = (float) gsettings.GetFogEnd();
|
|
}
|
|
|
|
// Save.
|
|
if (!config->base_path.IsEmpty()) {
|
|
scene->name = String::Format("%s/%s.nms", config->base_path.toUtf8(),
|
|
String(_input).CutFilePath().CutFileExtension().toUtf8());
|
|
NML::SaveToFile(*scene, scene->name);
|
|
scene->name = Platform::Get().io->StripRootPath(scene->name);
|
|
}
|
|
|
|
fbx_scene->Destroy();
|
|
ListDeleteAllPtr(ExportedNode *, node_list)
|
|
|
|
if (config->event_handler)
|
|
config->event_handler->EndLoad();
|
|
return true;
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
FBXImporter::FBXImporter() {
|
|
sdk_manager = FbxManager::Create();
|
|
fbx_scene = NULL;
|
|
}
|
|
|
|
FBXImporter::~FBXImporter() {
|
|
if (sdk_manager)
|
|
sdk_manager->Destroy();
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|