/* -----------------------------------------------------------------------------
	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();
}

//------------------------------------------------------------------------------
