//
//  KRResource+fbx.cpp
//  Kraken Engine
//
//  Copyright 2026 Kearwood Gilbert. All rights reserved.
//  
//  Redistribution and use in source and binary forms, with or without modification, are
//  permitted provided that the following conditions are met:
//  
//  1. Redistributions of source code must retain the above copyright notice, this list of
//  conditions and the following disclaimer.
//  
//  2. Redistributions in binary form must reproduce the above copyright notice, this list
//  of conditions and the following disclaimer in the documentation and/or other materials
//  provided with the distribution.
//  
//  THIS SOFTWARE IS PROVIDED BY KEARWOOD GILBERT ''AS IS'' AND ANY EXPRESS OR IMPLIED
//  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
//  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KEARWOOD GILBERT OR
//  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
//  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
//  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
//  ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//  
//  The views and conclusions contained in the software and documentation are those of the
//  authors and should not be interpreted as representing official policies, either expressed
//  or implied, of Kearwood Gilbert.
//

#include "KREngine-common.h"
#include "fbxsdk.h"


#include "KRResource.h"
#include "KRMesh.h"
#include "KRMaterial.h"
#include "KRLight.h"
#include "KRPointLight.h"
#include "KRDirectionalLight.h"
#include "KRSpotLight.h"
#include "KRNode.h"
#include "KRScene.h"
#include "KRBone.h"
#include "KRLocator.h"
#include "KRBundle.h"
#include "KRModel.h"
#include "KRLODGroup.h"
#include "KRLODSet.h"
#include "KRCollider.h"

#ifdef IOS_REF
#undef  IOS_REF
#define IOS_REF (*(pSdkManager->GetIOSettings()))
#endif

#define warning(e,s) if(!(e))KRContext::Log(KRContext::LOG_LEVEL_WARNING, "%s\n",s)

void InitializeSdkObjects(FbxManager*& pSdkManager, FbxScene*& pScene);
void DestroySdkObjects(FbxManager* pSdkManager);
bool LoadScene(FbxManager* pSdkManager, FbxDocument* pScene, const char* pFilename);
KRAnimation* LoadAnimation(KRContext& context, FbxAnimStack* pAnimStack);
KRAnimationCurve* LoadAnimationCurve(KRContext& context, FbxAnimCurve* pAnimCurve);
KRAnimationLayer* LoadAnimationLayer(KRContext& context, FbxAnimLayer* pAnimLayer);
void LoadNode(FbxScene* pFbxScene, KRNode* parent_node, FbxGeometryConverter* pGeometryConverter, FbxNode* pNode);
//void BakeNode(KFbxNode* pNode);
void LoadMaterial(KRContext& context, FbxSurfaceMaterial* pMaterial);
void LoadMesh(KRContext& context, FbxScene* pFbxScene, FbxGeometryConverter* pGeometryConverter, FbxMesh* pMesh);
KRNode* LoadMesh(KRNode* parent_node, FbxScene* pFbxScene, FbxGeometryConverter* pGeometryConverter, FbxNode* pNode);
KRNode* LoadLight(KRNode* parent_node, FbxNode* pNode);
KRNode* LoadSkeleton(KRNode* parent_node, FbxScene* pScene, FbxNode* pNode);
KRNode* LoadLocator(KRNode* parent_node, FbxScene* pScene, FbxNode* pNode);
KRNode* LoadCamera(KRNode* parent_node, FbxNode* pNode);
std::string GetFbxObjectName(FbxObject* obj);

const float KRAKEN_FBX_ANIMATION_FRAMERATE = 30.0f; // FINDME - This should be configurable


std::string GetFbxObjectName(FbxObject* obj)
{
  bool is_locator = false;
  FbxNode* node = FbxCast<FbxNode>(obj);
  if (node) {
    FbxNodeAttribute::EType attribute_type = (node->GetNodeAttribute()->GetAttributeType());
    if (attribute_type == FbxNodeAttribute::eNull) {
      FbxNull* pSourceNull = (FbxNull*)node->GetNodeAttribute();
      if (pSourceNull->Look.Get() == FbxNull::eCross) {
        is_locator = true;
      }
    }
  }


  // Object names from FBX files are now concatenated with the FBX numerical ID to ensure that they are unique
  // TODO - This should be updated to only add a prefix or suffix if needed to make the name unique
  if (is_locator) {
    // We do not rename locators
    return std::string(obj->GetName());
  } else if (strncmp(obj->GetName(), "so_", 3) == 0) {
    // An so_ prefix indicates that this is a "Scriptable Object" that should not have the name decorated;
    return obj->GetName();
  } else if (strcmp(obj->GetName(), "default_camera") == 0) {
    // There is currently support for rendering from only one camera, "default_camera".  We don't translate this node's name, so that animations can drive the camera
    return "default_camera";
  } else {
    std::stringstream st;
    st << "fbx_";
    st << obj->GetUniqueID();
    if (strlen(obj->GetName()) != 0) {
      st << "_";
      st << obj->GetName();
    }
    return st.str();
  }
}

void KRResource::LoadFbx(KRContext& context, const std::string& path)
{
  KRScene* pScene = new KRScene(context, KRResource::GetFileBase(path));
  context.getSceneManager()->add(pScene);

  FbxManager* lSdkManager = NULL;
  FbxScene* pFbxScene = NULL;
  bool lResult;
  FbxGeometryConverter* pGeometryConverter = NULL;

  // Prepare the FBX SDK.
  InitializeSdkObjects(lSdkManager, pFbxScene);

  // Initialize Geometry Converter
  pGeometryConverter = new FbxGeometryConverter(lSdkManager);

  // Load the scene.
  lResult = LoadScene(lSdkManager, pFbxScene, path.c_str());

  // Triangulate the scene.
  lResult = pGeometryConverter->Triangulate(pFbxScene, true);

  FbxNode* pNode = pFbxScene->GetRootNode();

  // ----====---- Bake pivots into transforms, as Kraken doesn't support them directly ----====----
  /*
  printf("Baking pivots...\n");

  if(pNode) {
      pNode->ResetPivotSetAndConvertAnimation();
  }
   */

   // ----====---- Import Animation Layers ----====----
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading animations...");
  int animation_count = pFbxScene->GetSrcObjectCount<FbxAnimStack>();
  for (int i = 0; i < animation_count; i++) {
    FbxAnimStack* animation = pFbxScene->GetSrcObject<FbxAnimStack>(i);
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Animation %i of %i: %s", i + 1, animation_count, animation->GetName());
    KRAnimation* new_animation = LoadAnimation(context, animation);
    if (new_animation) {
      context.getAnimationManager()->addAnimation(new_animation);
    }
  }

  // ----====---- Import Animation Curves ----====----
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading animation curves...");
  int curve_count = pFbxScene->GetSrcObjectCount<FbxAnimCurve>();
  for (int i = 0; i < curve_count; i++) {
    FbxAnimCurve* curve = pFbxScene->GetSrcObject<FbxAnimCurve>(i);
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Animation Curve %i of %i: %s", i + 1, curve_count, curve->GetName());
    KRAnimationCurve* new_curve = LoadAnimationCurve(context, curve);

    if (new_curve) {
      KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Adding a curve");
      context.getAnimationCurveManager()->addAnimationCurve(new_curve);
    }
  }

  // ----====---- Import Materials ----====----
  int material_count = pFbxScene->GetSrcObjectCount<FbxSurfaceMaterial>();
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "\nLoading materials...");
  for (int i = 0; i < material_count; i++) {
    FbxSurfaceMaterial* material = pFbxScene->GetSrcObject<FbxSurfaceMaterial>(i);
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Material %i of %i: %s", i + 1, material_count, material->GetName());
    LoadMaterial(context, material);
  }

  // ----====---- Import Meshes ----====----
  int mesh_count = pFbxScene->GetSrcObjectCount<FbxMesh>();
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading meshes...");
  for (int i = 0; i < mesh_count; i++) {
    FbxMesh* mesh = pFbxScene->GetSrcObject<FbxMesh>(i);

    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Mesh %i of %i: %s", i + 1, mesh_count, mesh->GetNode()->GetName());
    LoadMesh(context, pFbxScene, pGeometryConverter, mesh);
  }

  // ----====---- Import Textures ----====----
  int texture_count = pFbxScene->GetSrcObjectCount<FbxFileTexture>();

  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading textures...");
  for (int i = 0; i < texture_count; i++) {
    FbxFileTexture* texture = pFbxScene->GetSrcObject<FbxFileTexture>(i);
    const char* file_name = texture->GetFileName();
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Texture %i of %i: %s", i + 1, texture_count, (KRResource::GetFileBase(file_name) + "." + KRResource::GetFileExtension(file_name)).c_str());
    context.loadResource(file_name);
  }

  // ----====---- Import Scene Graph Nodes ----====----
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading scene graph...");
  if (pNode) {
    for (int i = 0; i < pNode->GetChildCount(); i++) {
      LoadNode(pFbxScene, pScene->getRootNode(), pGeometryConverter, pNode->GetChild(i));
    }
  }




  DestroySdkObjects(lSdkManager);
}

void InitializeSdkObjects(FbxManager*& pSdkManager, FbxScene*& pScene)
{
  // The first thing to do is to create the FBX SDK manager which is the 
  // object allocator for almost all the classes in the SDK.
  pSdkManager = FbxManager::Create();

  if (!pSdkManager) {
    KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Unable to create the FBX SDK manager");
    exit(0);
  }

  // create an IOSettings object
  FbxIOSettings* ios = FbxIOSettings::Create(pSdkManager, IOSROOT);
  pSdkManager->SetIOSettings(ios);

  // Load plugins from the executable directory
  FbxString lPath = FbxGetApplicationDirectory();
#if defined(_WIN32) || defined(_WIN64)
  FbxString lExtension = "dll";
#elif TARGET_OS_MAC
  FbxString lExtension = "dylib";
#elif TARGET_OS_UNIX
  FbxString lExtension = "so";
#elif
#error Unsupported Platform
#endif
  pSdkManager->LoadPluginsDirectory(lPath.Buffer(), lExtension.Buffer());

  // Create the entity that will hold the scene.
  pScene = FbxScene::Create(pSdkManager, "");
}

void DestroySdkObjects(FbxManager* pSdkManager)
{
  // Delete the FBX SDK manager. All the objects that have been allocated 
  // using the FBX SDK manager and that haven't been explicitly destroyed 
  // are automatically destroyed at the same time.
  if (pSdkManager) pSdkManager->Destroy();
  pSdkManager = NULL;
}


bool LoadScene(FbxManager* pSdkManager, FbxDocument* pScene, const char* pFilename)
{
  int lFileMajor, lFileMinor, lFileRevision;
  int lSDKMajor, lSDKMinor, lSDKRevision;
  //int lFileFormat = -1;
  int lAnimStackCount;
  bool lStatus;
  char lPassword[1024];

  // Get the file version number generate by the FBX SDK.
  FbxManager::GetFileFormatVersion(lSDKMajor, lSDKMinor, lSDKRevision);

  // Create an importer.
  FbxImporter* lImporter = FbxImporter::Create(pSdkManager, "");

  // Initialize the importer by providing a filename.
  const bool lImportStatus = lImporter->Initialize(pFilename, -1, pSdkManager->GetIOSettings());
  lImporter->GetFileVersion(lFileMajor, lFileMinor, lFileRevision);

  if (!lImportStatus) {
    FbxStatus& status = lImporter->GetStatus();

    KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Call to KFbxImporter::Initialize() failed.\nError returned: %s", status.GetErrorString());



    if (status.GetCode() == FbxStatus::EStatusCode::eInvalidFileVersion) {
      KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for this FBX SDK is %d.%d.%d", lSDKMajor, lSDKMinor, lSDKRevision);
      KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for file %s is %d.%d.%d", pFilename, lFileMajor, lFileMinor, lFileRevision);
    }

    return false;
  }

  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for this FBX SDK is %d.%d.%d", lSDKMajor, lSDKMinor, lSDKRevision);

  if (!lImporter->IsFBX()) {
    KRContext::Log(KRContext::LOG_LEVEL_ERROR, "ERROR Unrecognized FBX File");
    return false;
  }


  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "FBX version number for file %s is %d.%d.%d\n", pFilename, lFileMajor, lFileMinor, lFileRevision);

  // From this point, it is possible to access animation stack information without
  // the expense of loading the entire file.

  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Animation Stack Information");

  lAnimStackCount = lImporter->GetAnimStackCount();

  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "    Number of Animation Stacks: %d", lAnimStackCount);
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "    Current Animation Stack: \"%s\"", lImporter->GetActiveAnimStackName().Buffer());


  // Set the import states. By default, the import states are always set to 
  // true. The code below shows how to change these states.
  IOS_REF.SetBoolProp(IMP_FBX_MATERIAL, true);
  IOS_REF.SetBoolProp(IMP_FBX_TEXTURE, true);
  IOS_REF.SetBoolProp(IMP_FBX_LINK, true);
  IOS_REF.SetBoolProp(IMP_FBX_SHAPE, true);
  IOS_REF.SetBoolProp(IMP_FBX_GOBO, true);
  IOS_REF.SetBoolProp(IMP_FBX_ANIMATION, true);
  IOS_REF.SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true);

  // Import the scene.
  lStatus = lImporter->Import(pScene);

  if (lStatus == false && lImporter->GetStatus().GetCode() == FbxStatus::EStatusCode::ePasswordError) {
    printf("Please enter password: ");

    lPassword[0] = '\0';

    scanf("%s", lPassword);
    FbxString lString(lPassword);

    IOS_REF.SetStringProp(IMP_FBX_PASSWORD, lString);
    IOS_REF.SetBoolProp(IMP_FBX_PASSWORD_ENABLE, true);

    lStatus = lImporter->Import(pScene);

    if (lStatus == false && lImporter->GetStatus().GetCode() == FbxStatus::EStatusCode::ePasswordError) {
      printf("\nPassword is wrong, import aborted.\n");
    }
  }

  //    // ----====---- Start: Bake pivots into transforms, as Kraken doesn't support them directly ----====----
  //    
  //    printf("Baking pivots...\n");
  //    KFbxNode* pNode = ((KFbxScene*)pScene)->GetRootNode();
  ////    BakeNode(pNode);
  //    
  //    for(i = 0; i < lAnimStackCount; i++)
  //    {
  //        KFbxTakeInfo* lTakeInfo = lImporter->GetTakeInfo(i);
  //        
  //        printf("   Animation: \"%s\"\n", lTakeInfo->mName.Buffer());
  //        
  //        //pNode->ConvertPivotAnimationRecursive(lTakeInfo->mName.Buffer(), KFbxNode::eDestinationPivot, KRAKEN_FBX_ANIMATION_FRAMERATE);
  //        pNode->ResetPivotSetAndConvertAnimation();
  //        
  //    }
  ////    pNode->ConvertPivotAnimationRecursive(NULL, KFbxNode::eDestinationPivot, KRAKEN_FBX_ANIMATION_FRAMERATE);
  ////    pNode->UpdatePropertiesFromPivotsAndLimits();
  //    
  //    // ----====---- End: Bake pivots into transforms, as Kraken doesn't support them directly ----====----

  //    // ----====---- Bake pivots into transforms, as Kraken doesn't support them directly ----====----
  //    
  //    printf("Baking pivots...\n");
  //    KFbxNode* pNode = ((KFbxScene*)pScene)->GetRootNode();
  //    if(pNode) {
  //        pNode->ResetPivotSetAndConvertAnimation();
  //    }

      // Destroy the importer.
  lImporter->Destroy();

  return lStatus;
}

KRAnimation* LoadAnimation(KRContext& context, FbxAnimStack* pAnimStack)
{
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading animation: \"%s\"", pAnimStack->GetName());

  KRAnimation* new_animation = new KRAnimation(context, pAnimStack->GetName());
  int cLayers = pAnimStack->GetMemberCount<FbxAnimLayer>();
  new_animation->setDuration(pAnimStack->LocalStop.Get().GetSecondDouble());
  for (int iLayer = 0; iLayer < cLayers; iLayer++) {
    new_animation->addLayer(LoadAnimationLayer(context, pAnimStack->GetMember<FbxAnimLayer>(iLayer)));
  }
  return new_animation;
}

KRAnimationCurve* LoadAnimationCurve(KRContext& context, FbxAnimCurve* pAnimCurve)
{
  std::string name = GetFbxObjectName(pAnimCurve);
  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Loading animation curve: \"%s\"", name.c_str());
  FbxTimeSpan time_span;
  if (!pAnimCurve->GetTimeInterval(time_span)) {
    KRContext::Log(KRContext::LOG_LEVEL_ERROR, "Failed to get time interval.");
    return NULL;
  }

  float dest_frame_rate = 30.0f; // FINDME, TODO - This needs to be dynamic
  int frame_start = time_span.GetStart().GetSecondDouble() * dest_frame_rate;
  int frame_count = (time_span.GetStop().GetSecondDouble() * dest_frame_rate) - frame_start;

  KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "    animation start %d and frame count %d", frame_start, frame_count);

  KRAnimationCurve* new_curve = new KRAnimationCurve(context, name);
  new_curve->setFrameRate(dest_frame_rate);
  new_curve->setFrameStart(frame_start);
  new_curve->setFrameCount(frame_count);

  // Resample animation curve
  int last_frame = 0; // Used by FBX sdk for faster keyframe searches
  for (int frame_number = 0; frame_number < frame_count; frame_number++) {
    float frame_seconds = (frame_start + frame_number) / dest_frame_rate;
    FbxTime frame_time;
    frame_time.SetSecondDouble(frame_seconds);
    float frame_value = pAnimCurve->Evaluate(frame_time, &last_frame);
    //        printf("  Frame %i / %i: %.6f\n", frame_number, frame_count, frame_value);
    if (0 == frame_number) KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Value at starting key frame = %3.3f", frame_value);
    new_curve->setValue(frame_number + frame_start, frame_value);
    // BUG FIX Dec 2, 2013 .. changed frame_number to frame_number+frame_start
    // setValue(frame_number, frame_value) clamps the frame_number range between frame_start : frame_start+frame_count
  }

  return new_curve;
}

KRAnimationLayer* LoadAnimationLayer(KRContext& context, FbxAnimLayer* pAnimLayer)
{
  KRAnimationLayer* new_layer = new KRAnimationLayer(context);
  new_layer->setName(pAnimLayer->GetName());
  new_layer->setWeight(pAnimLayer->Weight.Get() / 100.0f);
  switch (pAnimLayer->BlendMode.Get()) {
  case FbxAnimLayer::eBlendAdditive:
    new_layer->setBlendMode(KRAnimationLayer::KRENGINE_ANIMATION_BLEND_MODE_ADDITIVE);
    break;
  case FbxAnimLayer::eBlendOverride:
    new_layer->setBlendMode(KRAnimationLayer::KRENGINE_ANIMATION_BLEND_MODE_OVERRIDE);
    break;
  case FbxAnimLayer::eBlendOverridePassthrough:
    new_layer->setBlendMode(KRAnimationLayer::KRENGINE_ANIMATION_BLEND_MODE_OVERRIDE_PASSTHROUGH);
    break;
  }
  switch (pAnimLayer->RotationAccumulationMode.Get()) {
  case FbxAnimLayer::eRotationByLayer:
    new_layer->setRotationAccumulationMode(KRAnimationLayer::KRENGINE_ANIMATION_ROTATION_ACCUMULATION_BY_LAYER);
    break;
  case FbxAnimLayer::eRotationByChannel:
    new_layer->setRotationAccumulationMode(KRAnimationLayer::KRENGINE_ANIMATION_ROTATION_ACCUMULATION_BY_CHANNEL);
    break;
  }
  switch (pAnimLayer->ScaleAccumulationMode.Get()) {
  case FbxAnimLayer::eScaleAdditive:
    new_layer->setScaleAccumulationMode(KRAnimationLayer::KRENGINE_ANIMATION_SCALE_ACCUMULATION_ADDITIVE);
    break;
  case FbxAnimLayer::eScaleMultiply:
    new_layer->setScaleAccumulationMode(KRAnimationLayer::KRENGINE_ANIMATION_SCALE_ACCUMULATION_MULTIPLY);
    break;
  }

  return new_layer;
}
//
//void BakeNode(KFbxNode *pNode) {
//    
//    pNode->SetPivotState(KFbxNode::eSourcePivot, KFbxNode::ePivotActive);
//    pNode->SetPivotState(KFbxNode::eDestinationPivot, KFbxNode::ePivotActive);
//
//    // Pass the current value to the source pivot.
////    *     - Rotation offset (Roff)
////    *     - Rotation pivot (Rp)
////    *     - Pre-rotation (Rpre)
////    *     - Post-rotation (Rpost)
////    *     - Scaling offset (Soff)
////    *     - Scaling pivot (Sp)
////    *     - Geometric translation (Gt)
////    *     - Geometric rotation (Gr)
////    *     - Geometric scaling (Gs)
//    /*
//    pNode->SetPostRotation(KFbxNode::eSourcePivot, pNode->PostRotation.Get());
//    pNode->SetPreRotation(KFbxNode::eSourcePivot, pNode->PreRotation.Get());
//    pNode->SetRotationOffset(KFbxNode::eSourcePivot, pNode->RotationOffset.Get());
//    pNode->SetScalingOffset(KFbxNode::eSourcePivot, pNode->ScalingOffset.Get());
//    pNode->SetRotationPivot(KFbxNode::eSourcePivot, pNode->RotationPivot.Get());
//    pNode->SetScalingPivot(KFbxNode::eSourcePivot, pNode->ScalingPivot.Get());
//    pNode->SetGeometricRotation(KFbxNode::eSourcePivot, pNode->GeometricRotation.Get());
//    pNode->SetGeometricTranslation(KFbxNode::eSourcePivot, pNode->GeometricTranslation.Get());
//    pNode->SetGeometricScaling(KFbxNode::eSourcePivot, pNode->GeometricScaling.Get());
//    pNode->SetRotationOrder(KFbxNode::eSourcePivot, pNode->RotationOrder.Get());
//     */
//    
//    // We want to set all these to 0 and bake them into the transforms.
//    KFbxVector4 lZero(0.0, 0.0, 0.0);
//    KFbxVector4 lOne(1.0, 1.0, 1.0);
//    pNode->SetPostRotation(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetPreRotation(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetRotationOffset(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetScalingOffset(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetRotationPivot(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetScalingPivot(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetGeometricRotation(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetGeometricTranslation(KFbxNode::eDestinationPivot, lZero);
//    pNode->SetGeometricScaling(KFbxNode::eDestinationPivot, lOne);
//    pNode->SetRotationOrder(KFbxNode::eDestinationPivot, eEULER_XYZ);
//    
//    
//    /*
//     FbxVector4 lZero(0,0,0);
//     FbxVector4 lOne(1,1,1);
//     pNode->SetPivotState(FbxNode::eSourcePivot, FbxNode::ePivotActive);
//     pNode->SetPivotState(FbxNode::eDestinationPivot, FbxNode::ePivotActive);
//    
//     EFbxRotationOrder lRotationOrder;
//     pNode->GetRotationOrder(FbxNode::eSourcePivot , lRotationOrder);
//     pNode->SetRotationOrder(FbxNode::eDestinationPivot , lRotationOrder);
//    
//     //For cameras and lights (without targets) let's compensate the postrotation.
//     if( pNode->GetCamera() || pNode->GetLight() )
//         {
//                if( !pNode->GetTarget() )
//                    {
//                            FbxVector4 lRV(90, 0, 0);
//                            if( pNode->GetCamera() )
//                                   lRV.Set(0, 90, 0);
//                    
//                            FbxVector4 prV = pNode->GetPostRotation(FbxNode::eSourcePivot);
//                            FbxAMatrix lSourceR;
//                            FbxAMatrix lR(lZero, lRV, lOne);
//                            FbxVector4 res = prV;
//                    
//                            // Rotation order don't affect post rotation, so just use the default XYZ order
//                            FbxRotationOrder rOrder;
//                            rOrder.V2M(lSourceR, res);
//                    
//                            lR = lSourceR * lR;
//                            rOrder.M2V(res, lR);
//                            prV = res;
//                            pNode->SetPostRotation(FbxNode::eSourcePivot, prV);
//                            pNode->SetRotationActive(true);
//                        }
//            
//                // Point light do not need to be adjusted (since they radiate in all the directions).
//                if( pNode->GetLight() && pNode->GetLight()->LightType.Get() == FbxLight::ePoint )
//                    {
//                            pNode->SetPostRotation(FbxNode::eSourcePivot, FbxVector4(0,0,0,0));
//                        }
//             }
//     // apply Pre rotations only on bones / end of chains
//     if( pNode->GetNodeAttribute() && pNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eSkeleton
//             || (pNode->GetMarker() && pNode->GetMarker()->GetType() == FbxMarker::eEffectorFK)
//             || (pNode->GetMarker() && pNode->GetMarker()->GetType() == FbxMarker::eEffectorIK) )
//         {
//                if( pNode->GetRotationActive() )
//                    {
//                           pNode->SetPreRotation(FbxNode::eDestinationPivot, pNode->GetPreRotation(FbxNode::eSourcePivot));
//                        }
//            
//                // No pivots on bones
//                pNode->SetRotationPivot(FbxNode::eDestinationPivot, lZero);
//                pNode->SetScalingPivot(FbxNode::eDestinationPivot, lZero);
//                pNode->SetRotationOffset(FbxNode::eDestinationPivot,lZero);
//                pNode->SetScalingOffset(FbxNode::eDestinationPivot, lZero);
//             }
//     else
//         {
//                // any other type: no pre-rotation support but...
//                pNode->SetPreRotation(FbxNode::eDestinationPivot, lZero);
//            
//                // support for rotation and scaling pivots.
//                pNode->SetRotationPivot(FbxNode::eDestinationPivot, pNode->GetRotationPivot(FbxNode::eSourcePivot));
//                pNode->SetScalingPivot(FbxNode::eDestinationPivot, pNode->GetScalingPivot(FbxNode::eSourcePivot));
//                // Rotation and scaling offset are supported
//                pNode->SetRotationOffset(FbxNode::eDestinationPivot, pNode->GetRotationOffset(FbxNode::eSourcePivot));
//                pNode->SetScalingOffset(FbxNode::eDestinationPivot, pNode->GetScalingOffset(FbxNode::eSourcePivot));
//                //
//             // If we don't "support" scaling pivots, we can simply do:
//                // pNode->SetRotationPivot(FbxNode::eDestinationPivot, lZero);
//                // pNode->SetScalingPivot(FbxNode::eDestinationPivot, lZero);
//        }
//    */
//    
//    // Bake child nodes
//    for(int i = 0; i < pNode->GetChildCount(); i++)
//    {
//        BakeNode(pNode->GetChild(i));
//    }
//}

void LoadNode(FbxScene* pFbxScene, KRNode* parent_node, FbxGeometryConverter* pGeometryConverter, FbxNode* pNode)
{
  FbxVector4 lTmpVector;
  pNode->UpdatePropertiesFromPivotsAndLimits();


  FbxSystemUnit system_unit = pFbxScene->GetGlobalSettings().GetSystemUnit();

  // Transform = T * Roff * Rp * Rpre * R * Rpost * inverse(Rp) * Soff * Sp * S * inverse(Sp)

  int node_has_n_points = 0; // this will be 3 if the node_frame_key_position is complete after the import animated properties loop
  Vector3 node_key_frame_position = Vector3(0.0, 0.0, 0.0);
  // ADDED 3, 2013 by Peter to store the key frame (start location) of an animation
  // the x, y, z translation position of the animation will be extracted from the curves
  // as they are added to the animation layer in the loop below ..

// Import animated properties
  int animation_count = pFbxScene->GetSrcObjectCount<FbxAnimStack>();
  for (int i = 0; i < animation_count; i++) {
    //        FbxAnimStack* pAnimStack = FbxCast<FbxAnimStack>(pFbxScene->GetSrcObject(FBX_TYPE(FbxAnimStack), i));
    FbxAnimStack* pAnimStack = pFbxScene->GetSrcObject<FbxAnimStack>(i);
    KRAnimation* pAnimation = parent_node->getContext().getAnimationManager()->getAnimation(pAnimStack->GetName());
    if (pAnimation) {
      int cLayers = pAnimStack->GetMemberCount<FbxAnimLayer>();
      for (int iLayer = 0; iLayer < cLayers; iLayer++) {
        FbxAnimLayer* pFbxAnimLayer = pAnimStack->GetMember<FbxAnimLayer>(iLayer);
        //                float weight = pFbxAnimLayer->Weight.Get();
        KRAnimationLayer* pAnimationLayer = pAnimation->getLayer(pFbxAnimLayer->GetName());

        FbxAnimCurve* pAnimCurve = pNode->LclRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclTranslation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_TRANSLATE_X);

          KRAnimationCurve* curve = new_attribute->getCurve();
          node_key_frame_position.x = curve->getValue(curve->getFrameStart());
          node_has_n_points++;
          // ADDED Dec 3, 2013 by Peter to extract start location key frame

          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclTranslation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_TRANSLATE_Y);

          KRAnimationCurve* curve = new_attribute->getCurve();
          node_key_frame_position.y = curve->getValue(curve->getFrameStart());
          node_has_n_points++;
          // ADDED Dec 3, 2013 by Peter to extract start location key frame

          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclTranslation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_TRANSLATE_Z);

          KRAnimationCurve* curve = new_attribute->getCurve();
          node_key_frame_position.z = curve->getValue(curve->getFrameStart());
          node_has_n_points++;
          // ADDED Dec 3, 2013 by Peter to extract start location key frame

          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclScaling.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclScaling.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->LclScaling.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PreRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PreRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PreRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_PRE_ROTATION_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PostRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PostRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->PostRotation.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_POST_ROTATION_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }


        pAnimCurve = pNode->RotationPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->RotationPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->RotationPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATION_PIVOT_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->ScalingPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->ScalingPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->ScalingPivot.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_SCALE_PIVOT_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }


        pAnimCurve = pNode->RotationOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->RotationOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->RotationOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_ATTRIBUTE_ROTATE_OFFSET_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }


        pAnimCurve = pNode->ScalingOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_X);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_SCALE_OFFSET_X);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->ScalingOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Y);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_SCALE_OFFSET_Y);
          pAnimationLayer->addAttribute(new_attribute);
        }

        pAnimCurve = pNode->ScalingOffset.GetCurve(pFbxAnimLayer, FBXSDK_CURVENODE_COMPONENT_Z);
        if (pAnimCurve) {
          KRAnimationAttribute* new_attribute = new KRAnimationAttribute(parent_node->getContext());
          new_attribute->setCurveName(GetFbxObjectName(pAnimCurve));
          new_attribute->setTargetName(GetFbxObjectName(pNode));
          new_attribute->setTargetAttribute(KRNode::KRENGINE_NODE_SCALE_OFFSET_Z);
          pAnimationLayer->addAttribute(new_attribute);
        }
      }
    }
  }

  FbxDouble3 local_rotation = pNode->LclRotation.Get(); // pNode->GetGeometricRotation(KFbxNode::eSourcePivot);
  FbxDouble3 local_translation = pNode->LclTranslation.Get(); // pNode->GetGeometricTranslation(KFbxNode::eSourcePivot);
  FbxDouble3 local_scale = pNode->LclScaling.Get(); // pNode->GetGeometricScaling(KFbxNode::eSourcePivot);

  bool rotation_active = pNode->RotationActive.Get();

  FbxDouble3 post_rotation = pNode->PostRotation.Get();
  FbxDouble3 pre_rotation = pNode->PreRotation.Get();
  FbxDouble3 rotation_offset = pNode->RotationOffset.Get();
  FbxDouble3 scaling_offset = pNode->ScalingOffset.Get();
  FbxDouble3 rotation_pivot = pNode->RotationPivot.Get();
  FbxDouble3 scaling_pivot = pNode->ScalingPivot.Get();
  FbxDouble3 geometric_rotation = pNode->GeometricRotation.Get();
  FbxDouble3 geometric_translation = pNode->GeometricTranslation.Get();
  FbxDouble3 geometric_scaling = pNode->GeometricScaling.Get();
  EFbxRotationOrder rotation_order = pNode->RotationOrder.Get();


  FbxVector4 lZero(0.0, 0.0, 0.0);
  FbxVector4 lOne(1.0, 1.0, 1.0);

  warning((geometric_rotation == lZero), "Geometric Rotation not supported .. 3DSMax file??");
  warning((geometric_translation == lZero), "Geometric Rotation not supported .. 3DSMax file??");
  warning((geometric_scaling == lOne), "Geometric Rotation not supported .. 3DSMax file??");
  warning((rotation_order == eEulerXYZ), "Geometric Rotation not supported .. 3DSMax file??");

  // FINDME - node_key_frame_position contains the key frame (start location) for an animation node
  // node_has_n_points
  // node_key_frame_position
  if (3 == node_has_n_points)
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "key frame at %2.3f,  %2.3f, %2.3f",
           node_key_frame_position.x,
           node_key_frame_position.y,
           node_key_frame_position.z);
  //
  // The animation curve data is output to the KRNode::KRENGINE_NODE_ATTRIBUTE_TRANSLATE_ X, Y, Z attribute targets
  // The actor scripts 'move' command modifies the node's world translations using setWorldTranslation()
  //
  // QUESTION - where should we store the key frame within the node to make sure it is output into the KRBundle ??
  //              do we store it as node_local or store it as the world translation? or somewhere else ??
  //

  Vector3 node_translation = Vector3(local_translation[0], local_translation[1], local_translation[2]); // T * Roff * Rp
  Vector3 node_rotation = Vector3(local_rotation[0], local_rotation[1], local_rotation[2]) / 180.0 * M_PI;
  Vector3 node_scale = Vector3(local_scale[0], local_scale[1], local_scale[2]);

  Vector3 node_rotation_offset = Vector3(rotation_offset[0], rotation_offset[1], rotation_offset[2]);
  Vector3 node_scaling_offset = Vector3(scaling_offset[0], scaling_offset[1], scaling_offset[2]);
  Vector3 node_rotation_pivot = Vector3(rotation_pivot[0], rotation_pivot[1], rotation_pivot[2]);
  Vector3 node_scaling_pivot = Vector3(scaling_pivot[0], scaling_pivot[1], scaling_pivot[2]);
  Vector3 node_pre_rotation, node_post_rotation;
  if (rotation_active) {
    node_pre_rotation = Vector3(pre_rotation[0], pre_rotation[1], pre_rotation[2]) / 180.0 * M_PI;
    node_post_rotation = Vector3(post_rotation[0], post_rotation[1], post_rotation[2]) / 180.0 * M_PI;
    //&KRF HACK removing this line (above) to prevent the post rotation from corrupting the default light values; the FBX is importing a post rotation and setting it to -90 degrees											
  } else {
    node_pre_rotation = Vector3::Zero();
    node_post_rotation = Vector3::Zero();
  }

  //    printf("        Local Translation:      %f %f %f\n", local_translation[0], local_translation[1], local_translation[2]);
  //    printf("        Local Rotation:         %f %f %f\n", local_rotation[0], local_rotation[1], local_rotation[2]);
  //    printf("        Local Scaling:          %f %f %f\n", local_scale[0], local_scale[1], local_scale[2]);

  FbxNodeAttribute::EType attribute_type = (pNode->GetNodeAttribute()->GetAttributeType());
  if (attribute_type == FbxNodeAttribute::eLODGroup) {
    std::string name = GetFbxObjectName(pNode);
    FbxLODGroup* fbx_lod_group = (FbxLODGroup*)pNode->GetNodeAttribute(); // FbxCast<FbxLODGroup>(pNode);
    bool use_world_space_units = fbx_lod_group->WorldSpace.Get();
    float group_min_distance = 0.0f;
    float group_max_distance = 0.0f;
    if (fbx_lod_group->MinMaxDistance.Get()) {
      group_min_distance = fbx_lod_group->MinDistance.Get();
      group_max_distance = fbx_lod_group->MaxDistance.Get();
    }

    KRLODSet* lod_set = new KRLODSet(parent_node->getScene(), name);
    parent_node->appendChild(lod_set);

    AABB reference_bounds;
    // Create a lod_group node for each fbx child node
    int child_count = pNode->GetChildCount();
    for (int i = 0; i < child_count; i++) {
      float min_distance = 0;
      float max_distance = 0; // 0 for max_distance means infinity
      FbxLODGroup::EDisplayLevel display_level;
      fbx_lod_group->GetDisplayLevel(i, display_level);
      switch (display_level) {
      case FbxLODGroup::eUseLOD:
        if (i > 0) {
          FbxDistance d;
          fbx_lod_group->GetThreshold(i - 1, d);
          min_distance = d.valueAs(system_unit);
        }
        if (i < child_count - 1) {
          FbxDistance d;
          fbx_lod_group->GetThreshold(i, d);
          max_distance = d.valueAs(system_unit);
        }
        break;
      case FbxLODGroup::eShow:
        // We leave min_distance and max_distance as 0's, which effectively makes the LOD group always visible
        break;
      case FbxLODGroup::eHide:
        min_distance = -1;
        max_distance = -1;
        // LOD Groups with -1 for both min_distance and max_distance will never be displayed; import in case that the distance values are to be modified by scripting at runtime
        break;
      }

      if (group_min_distance != 0.0f && min_distance != -1) {
        if (min_distance < group_min_distance) min_distance = group_min_distance;
      }
      if (group_max_distance != 0.0f && max_distance != -1) {
        if (max_distance == 0.0f) {
          max_distance = group_max_distance;
        } else if (max_distance > group_max_distance) {
          max_distance = group_max_distance;
        }
      }

      KRLODGroup* new_node = new KRLODGroup(parent_node->getScene(), name + "_lodlevel" + boost::lexical_cast<string>(i + 1));
      new_node->setMinDistance(min_distance);
      new_node->setMaxDistance(max_distance);
      new_node->setLocalRotation(node_rotation);
      new_node->setLocalTranslation(node_translation);
      new_node->setLocalScale(node_scale);


      new_node->setRotationOffset(node_rotation_offset);
      new_node->setScalingOffset(node_scaling_offset);
      new_node->setRotationPivot(node_rotation_pivot);
      new_node->setScalingPivot(node_scaling_pivot);
      new_node->setPreRotation(node_pre_rotation);
      new_node->setPostRotation(node_post_rotation);

      new_node->setUseWorldUnits(use_world_space_units);
      lod_set->appendChild(new_node);

      LoadNode(pFbxScene, new_node, pGeometryConverter, pNode->GetChild(i));

      if (i == 0) {
        // Calculate reference point using the bounding box center from the highest quality LOD level
        reference_bounds = new_node->getBounds();
      }

      new_node->setReference(AABB(reference_bounds.min, reference_bounds.max, new_node->getInverseModelMatrix()));
    }
  } else {
    KRNode* new_node = NULL;
    switch (attribute_type) {
    case FbxNodeAttribute::eMesh:
      new_node = LoadMesh(parent_node, pFbxScene, pGeometryConverter, pNode);
      break;
    case FbxNodeAttribute::eLight:
      new_node = LoadLight(parent_node, pNode);
      break;
    case FbxNodeAttribute::eSkeleton:
      new_node = LoadSkeleton(parent_node, pFbxScene, pNode);
      break;

    case FbxNodeAttribute::eCamera:
      new_node = LoadCamera(parent_node, pNode);
      break;
    default:
    {
      bool is_locator = false;
      if (attribute_type == FbxNodeAttribute::eNull) {
        FbxNull* pSourceNull = (FbxNull*)pNode->GetNodeAttribute();
        if (pSourceNull->Look.Get() == FbxNull::eCross) {
          is_locator = true;
        }
      }

      if (is_locator) {
        new_node = LoadLocator(parent_node, pFbxScene, pNode);
      } else {
        if (pNode->GetChildCount() > 0) {
          // Create an empty node, for inheritence of transforms
          std::string name = GetFbxObjectName(pNode);
          new_node = new KRNode(parent_node->getScene(), name);
        }
      }
    }
    break;
    }

    // this is a KRNode
    if (new_node != NULL) {
      new_node->setLocalRotation(node_rotation);
      new_node->setLocalTranslation(node_translation);
      new_node->setLocalScale(node_scale);
      new_node->setRotationOffset(node_rotation_offset);
      new_node->setScalingOffset(node_scaling_offset);
      new_node->setRotationPivot(node_rotation_pivot);
      new_node->setScalingPivot(node_scaling_pivot);
      new_node->setPreRotation(node_pre_rotation);
      new_node->setPostRotation(node_post_rotation);
      parent_node->appendChild(new_node);

      // Load child nodes
      for (int i = 0; i < pNode->GetChildCount(); i++) {
        LoadNode(pFbxScene, new_node, pGeometryConverter, pNode->GetChild(i));
      }
    }
  }
}

void LoadMaterial(KRContext& context, FbxSurfaceMaterial* pMaterial)
{
  //printf("  %s: %i - %i\n", pMaterial->GetName(), mat_vertex_start, mat_vertex_count + mat_vertex_start - 1);

  // ----====---- Output Material File ----====----
  KRMaterial* new_material = new KRMaterial(context, pMaterial->GetName());

  std::string name = pMaterial->GetName();
  if (boost::starts_with(name, "ab_reflect_")) {
    new_material->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDONESIDE);
    size_t underscore_pos = name.find('_', 11);
    new_material->setReflectionCube(name.substr(11, underscore_pos - 11));
  } else if (boost::starts_with(name, "reflect_")) {
    size_t underscore_pos = name.find('_', 8);
    new_material->setReflectionCube(name.substr(8, underscore_pos - 8));
  } else if (boost::starts_with(name, "at_")) {
    new_material->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_TEST);
  } else if (boost::starts_with(name, "ab_")) {
    new_material->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDONESIDE);
  } else if (boost::starts_with(name, "ab2_")) {
    new_material->setAlphaMode(KRMaterial::KRMATERIAL_ALPHA_MODE_BLENDTWOSIDE);
  }

  FbxPropertyT<FbxDouble3> lKFbxDouble3;
  FbxPropertyT<FbxDouble> lKFbxDouble1;

  if (pMaterial->GetClassId().Is(FbxSurfacePhong::ClassId)) {
    // We found a Phong material.

    // Ambient Color
    lKFbxDouble3 = ((FbxSurfacePhong*)pMaterial)->Ambient;
    new_material->setAmbient(Vector3(lKFbxDouble3.Get()[0], lKFbxDouble3.Get()[1], lKFbxDouble3.Get()[2]));

    // Diffuse Color
    lKFbxDouble3 = ((FbxSurfacePhong*)pMaterial)->Diffuse;
    new_material->setDiffuse(Vector3(lKFbxDouble3.Get()[0], lKFbxDouble3.Get()[1], lKFbxDouble3.Get()[2]));

    // Specular Color (unique to Phong materials)
    lKFbxDouble3 = ((FbxSurfacePhong*)pMaterial)->Specular;
    new_material->setSpecular(Vector3(lKFbxDouble3.Get()[0], lKFbxDouble3.Get()[1], lKFbxDouble3.Get()[2]));

    // Emissive Color
    //lKFbxDouble3 =((KFbxSurfacePhong *) pMaterial)->Emissive;

    /*
    // Transparency
    lKFbxDouble1 =((KFbxSurfacePhong *) pMaterial)->TransparencyFactor;
    new_material->setTransparency(1.0f - lKFbxDouble1.Get());
    */

    // Shininess
    lKFbxDouble1 = ((FbxSurfacePhong*)pMaterial)->Shininess;
    new_material->setShininess(lKFbxDouble1.Get());

    /*
    // Specular Factor
    lKFbxDouble1 =((KFbxSurfacePhong *) pMaterial)->SpecularFactor;
    double specular_factor = lKFbxDouble1.Get();
     */

     // Transparency Color
    lKFbxDouble3 = ((FbxSurfacePhong*)pMaterial)->TransparentColor;
    new_material->setTransparency(1.0f - (lKFbxDouble3.Get()[0] + lKFbxDouble3.Get()[1] + lKFbxDouble3.Get()[2]) / 3.0f);

    // Reflection factor
    lKFbxDouble1 = ((FbxSurfacePhong*)pMaterial)->ReflectionFactor;

    // Reflection color
    //lKFbxDouble3 =((FbxSurfacePhong *) pMaterial)->Reflection;

    // We modulate Relection color by reflection factor, as we only have one "reflection color" variable in Kraken
    new_material->setReflection(Vector3(/*lKFbxDouble3.Get()[0] * */lKFbxDouble1.Get(), /*lKFbxDouble3.Get()[1] * */lKFbxDouble1.Get(), /*lKFbxDouble3.Get()[2] * */lKFbxDouble1.Get()));

  } else if (pMaterial->GetClassId().Is(FbxSurfaceLambert::ClassId)) {
    // We found a Lambert material.

    // Ambient Color
    lKFbxDouble3 = ((FbxSurfaceLambert*)pMaterial)->Ambient;
    new_material->setAmbient(Vector3(lKFbxDouble3.Get()[0], lKFbxDouble3.Get()[1], lKFbxDouble3.Get()[2]));

    // Diffuse Color
    lKFbxDouble3 = ((FbxSurfaceLambert*)pMaterial)->Diffuse;
    new_material->setDiffuse(Vector3(lKFbxDouble3.Get()[0], lKFbxDouble3.Get()[1], lKFbxDouble3.Get()[2]));

    // Emissive
    //lKFbxDouble3 =((KFbxSurfaceLambert *)pMaterial)->Emissive;
    /*
    // Opacity
    lKFbxDouble1 =((KFbxSurfaceLambert *)pMaterial)->TransparencyFactor;
    new_material->setTransparency(1.0f - lKFbxDouble1.Get());
     */

     // Transparency Color
    lKFbxDouble3 = ((FbxSurfaceLambert*)pMaterial)->TransparentColor;
    new_material->setTransparency(1.0f - (lKFbxDouble3.Get()[0] + lKFbxDouble3.Get()[1] + lKFbxDouble3.Get()[2]) / 3.0f);

  } else {
    KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Unable to convert material: %s", pMaterial->GetName());
  }

  FbxProperty pProperty;

  // Diffuse Map Texture
  pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sDiffuse);
  if (pProperty.GetSrcObjectCount(FbxCriteria::ObjectType(FbxLayeredTexture::ClassId)) > 0) {
    KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Layered textures not supported.");
  }

  int texture_count = pProperty.GetSrcObjectCount(FbxCriteria::ObjectType(FbxTexture::ClassId));
  if (texture_count > 1) {
    KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Multiple diffuse textures not supported.");
  } else if (texture_count == 1) {
    FbxTexture* pTexture = FbxCast <FbxTexture>(pProperty.GetSrcObject(FbxCriteria::ObjectType(FbxTexture::ClassId), 0));
    assert(!pTexture->GetSwapUV());
    assert(pTexture->GetCroppingTop() == 0);
    assert(pTexture->GetCroppingLeft() == 0);
    assert(pTexture->GetCroppingRight() == 0);
    assert(pTexture->GetCroppingBottom() == 0);
    assert(pTexture->GetWrapModeU() == FbxTexture::eRepeat);
    assert(pTexture->GetWrapModeV() == FbxTexture::eRepeat);
    assert(pTexture->GetRotationU() == 0.0f);
    assert(pTexture->GetRotationV() == 0.0f);
    assert(pTexture->GetRotationW() == 0.0f);

    FbxFileTexture* pFileTexture = FbxCast<FbxFileTexture>(pTexture);
    if (pFileTexture) {
      new_material->setDiffuseMap(KRResource::GetFileBase(pFileTexture->GetFileName()), Vector2(pTexture->GetScaleU(), pTexture->GetScaleV()), Vector2(pTexture->GetTranslationU(), pTexture->GetTranslationV()));
    }
  }


  // Specular Map Texture
  pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sSpecular);
  if (pProperty.GetSrcObjectCount(FbxCriteria::ObjectType(FbxLayeredTexture::ClassId)) > 0) {
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Layered textures not supported.");
  }
  texture_count = pProperty.GetSrcObjectCount(FbxCriteria::ObjectType(FbxTexture::ClassId));
  if (texture_count > 1) {
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Multiple specular textures not supported.");
  } else if (texture_count == 1) {
    FbxTexture* pTexture = FbxCast <FbxTexture>(pProperty.GetSrcObject(FbxCriteria::ObjectType(FbxTexture::ClassId), 0));
    FbxFileTexture* pFileTexture = FbxCast<FbxFileTexture>(pTexture);
    if (pFileTexture) {
      new_material->setSpecularMap(KRResource::GetFileBase(pFileTexture->GetFileName()), Vector2(pTexture->GetScaleU(), pTexture->GetScaleV()), Vector2(pTexture->GetTranslationU(), pTexture->GetTranslationV()));
    }
  }

  // Normal Map Texture
  pProperty = pMaterial->FindProperty(FbxSurfaceMaterial::sNormalMap);
  if (pProperty.GetSrcObjectCount(FbxCriteria::ObjectType(FbxLayeredTexture::ClassId)) > 0) {
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Layered textures not supported.");
  }


  texture_count = pProperty.GetSrcObjectCount<FbxTexture>();
  if (texture_count > 1) {
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Multiple normal map textures not supported.");
  } else if (texture_count == 1) {
    FbxTexture* pTexture = pProperty.GetSrcObject<FbxTexture>(0);
    FbxFileTexture* pFileTexture = FbxCast<FbxFileTexture>(pTexture);
    if (pFileTexture) {
      new_material->setNormalMap(KRResource::GetFileBase(pFileTexture->GetFileName()), Vector2(pTexture->GetScaleU(), pTexture->GetScaleV()), Vector2(pTexture->GetTranslationU(), pTexture->GetTranslationV()));
    }
  }

  // Only save unique materials
  KRMaterial* found_material = context.getMaterialManager()->getMaterial(new_material->getName());
  if (found_material == NULL) {
    context.getMaterialManager()->add(new_material);
  } else {
    delete new_material;
  }
}



void LoadMesh(KRContext& context, FbxScene* pFbxScene, FbxGeometryConverter* pGeometryConverter, FbxMesh* pMesh)
{
  KRMesh::mesh_info mi;
  mi.format = Topology::Triangles;

  typedef struct
  {
    float weights[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX];
    int bone_indexes[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX];
  } control_point_weight_info_t;

  int control_point_count = pMesh->GetControlPointsCount();
  FbxVector4* control_points = pMesh->GetControlPoints();

  control_point_weight_info_t* control_point_weights = new control_point_weight_info_t[control_point_count];
  for (int control_point = 0; control_point < control_point_count; control_point++) {
    for (int i = 0; i < KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX; i++) {
      control_point_weights[control_point].weights[i] = 0.0f;
      control_point_weights[control_point].bone_indexes[i] = 0;
    }

  }

  bool too_many_bone_weights = false;

  // Collect the top 4 bone weights per vertex ...
  int skin_count = pMesh->GetDeformerCount(FbxDeformer::eSkin);
  int target_bone_index = 0;
  for (int skin_index = 0; skin_index < skin_count; skin_index++) {
    FbxSkin* skin = (FbxSkin*)pMesh->GetDeformer(skin_index, FbxDeformer::eSkin);
    int cluster_count = skin->GetClusterCount();
    KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "  Found skin with %i clusters.\n", cluster_count);
    for (int cluster_index = 0; cluster_index < cluster_count; cluster_index++) {
      FbxCluster* cluster = skin->GetCluster(cluster_index);
      if (cluster->GetLinkMode() != FbxCluster::eNormalize) {
        KRContext::Log(KRContext::LOG_LEVEL_WARNING, "    Warning!  link mode not supported.");
      }
      std::string bone_name = GetFbxObjectName(cluster->GetLink());
      mi.bone_names.push_back(bone_name);

      /*
      FbxMatrix fbx_bind_pose_matrix;
      Matrix4 bind_pose;
      PoseList pose_list;
      FbxArray<int> pose_indices;
      if(FbxPose::GetBindPoseContaining(pFbxScene, cluster->GetLink(), pose_list, pose_indices)) {
          fbx_bind_pose_matrix = (*pose_list)[0].GetMatrix(pose_indices[0]);
          bind_pose = Matrix4(
             Vector3(fbx_bind_pose_matrix.GetColumn(0).mData),
             Vector3(fbx_bind_pose_matrix.GetColumn(1).mData),
             Vector3(fbx_bind_pose_matrix.GetColumn(2).mData),
             Vector3(fbx_bind_pose_matrix.GetColumn(3).mData)
          );
          fprintf(stderr, "Found bind pose(s)!\n");
      }
      */

      FbxAMatrix link_matrix;
      cluster->GetTransformLinkMatrix(link_matrix);
      mi.bone_bind_poses.push_back(Matrix4(
        Vector3(link_matrix.GetColumn(0).mData),
        Vector3(link_matrix.GetColumn(1).mData),
        Vector3(link_matrix.GetColumn(2).mData),
        Vector3(link_matrix.GetColumn(3).mData)
      ));

      int cluster_control_point_count = cluster->GetControlPointIndicesCount();
      for (int cluster_control_point = 0; cluster_control_point < cluster_control_point_count; cluster_control_point++) {
        int control_point = cluster->GetControlPointIndices()[cluster_control_point];
        control_point_weight_info_t& weight_info = control_point_weights[control_point];
        float bone_weight = cluster->GetControlPointWeights()[cluster_control_point];
        if (bone_weight > weight_info.weights[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX - 1]) {
          if (weight_info.weights[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX - 1] != 0.0f) {
            too_many_bone_weights = true;
          }
          weight_info.weights[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX - 1] = bone_weight;
          weight_info.bone_indexes[KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX - 1] = target_bone_index;
          for (int bone_index = KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX - 2; bone_index >= 0; bone_index--) {
            if (bone_weight > weight_info.weights[bone_index]) {
              weight_info.weights[bone_index + 1] = weight_info.weights[bone_index];
              weight_info.bone_indexes[bone_index + 1] = weight_info.bone_indexes[bone_index];
              weight_info.weights[bone_index] = bone_weight;
              weight_info.bone_indexes[bone_index] = target_bone_index;
            }
          }
        } else {
          too_many_bone_weights = true;
        }
      }
      target_bone_index++;
    }
  }

  if (too_many_bone_weights) {
    KRContext::Log(KRContext::LOG_LEVEL_WARNING, "Clipped bone weights to limit of %i per vertex (selecting largest weights and re-normalizing).", KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX);
  }
  // Normalize bone weights
  if (mi.bone_names.size() > 0) {
    for (int control_point_index = 0; control_point_index < control_point_count; control_point_index++) {
      control_point_weight_info_t& weight_info = control_point_weights[control_point_index];
      float total_weights = 0.0f;
      for (int i = 0; i < KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX; i++) {
        total_weights += weight_info.weights[i];
      }
      if (total_weights == 0.0f) total_weights = 1.0f; // Prevent any divisions by zero
      for (int i = 0; i < KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX; i++) {
        weight_info.weights[i] = weight_info.weights[i] / total_weights;
      }
    }
  }

  pMesh->GenerateTangentsDataForAllUVSets(true);

  int polygon_count = pMesh->GetPolygonCount();
  int uv_count = pMesh->GetElementUVCount();
  int normal_count = pMesh->GetElementNormalCount();
  int tangent_count = pMesh->GetElementTangentCount();
  int elementmaterial_count = pMesh->GetElementMaterialCount();
  int material_count = pMesh->GetNode()->GetMaterialCount(); // FINDME, TODO - To support instancing, material names should be stored in the instance rather than the mesh

//    std::vector<std::vector<float> > bone_weights;
//    std::vector<std::vector<int> > bone_indexes;
//    
//    std::vector<Vector3> vertices;
//    std::vector<Vector2> texcoord0;
//    std::vector<Vector2> uvb;
//    std::vector<Vector3> normals;
//    std::vector<Vector3> tangents;
//    std::vector<int> submesh_lengths;
//    std::vector<int> submesh_starts;
//    std::vector<std::string> material_names;

  int dest_vertex_id = 0;

  bool need_tangents = false;

  for (int iMaterial = 0; iMaterial < material_count; iMaterial++) {
    FbxSurfaceMaterial* pMaterial = pMesh->GetNode()->GetMaterial(iMaterial);

    KRMaterial* material = context.getMaterialManager()->getMaterial(pMaterial->GetName());
    if (material) {
      if (material->needsVertexTangents()) {
        need_tangents = true;
      }
    }

    int source_vertex_id = 0;
    int mat_vertex_count = 0;
    int mat_vertex_start = dest_vertex_id;
    for (int iPolygon = 0; iPolygon < polygon_count; iPolygon++) {
      int lPolygonSize = pMesh->GetPolygonSize(iPolygon);
      if (lPolygonSize != 3) {
        source_vertex_id += lPolygonSize;
        KRContext::Log(KRContext::LOG_LEVEL_INFORMATION, "Poly with %i vertices found. Expecting only triangles.", lPolygonSize);
      } else {
        // ----====---- Read SubMesh / Material Mapping ----====----
        int iNewMaterial = -1;
        for (int l = 0; l < elementmaterial_count; l++) {
          FbxGeometryElementMaterial* leMat = pMesh->GetElementMaterial(l);
          if (leMat) {
            if (leMat->GetReferenceMode() == FbxGeometryElement::eIndex || leMat->GetReferenceMode() == FbxGeometryElement::eIndexToDirect) {
              int new_id = leMat->GetIndexArray().GetAt(iPolygon);
              if (new_id >= 0) {
                iNewMaterial = new_id;
              }
            }
          }
        }

        if (iMaterial == iNewMaterial) {
          // ----====---- Read Vertex-level Attributes ----====----
          for (int iVertex = 0; iVertex < 3; iVertex++) {
            // ----====---- Read Vertex Position ----====----
            int lControlPointIndex = pMesh->GetPolygonVertex(iPolygon, iVertex);
            FbxVector4 v = control_points[lControlPointIndex];
            mi.vertices.push_back(Vector3(v[0], v[1], v[2]));

            if (mi.bone_names.size() > 0) {
              control_point_weight_info_t& weight_info = control_point_weights[lControlPointIndex];
              std::vector<int> vertex_bone_indexes;
              std::vector<float> vertex_bone_weights;
              for (int i = 0; i < KRENGINE_MAX_BONE_WEIGHTS_PER_VERTEX; i++) {
                vertex_bone_indexes.push_back(weight_info.bone_indexes[i]);
                vertex_bone_weights.push_back(weight_info.weights[i]);
              }
              mi.bone_indexes.push_back(vertex_bone_indexes);
              mi.bone_weights.push_back(vertex_bone_weights);
            }

            Vector2 new_uva = Vector2(0.0, 0.0);
            Vector2 new_uvb = Vector2(0.0, 0.0);


            // ----====---- Read UVs ----====----

            FbxStringList uvNames;
            pMesh->GetUVSetNames(uvNames);
            if (uv_count >= 1) {
              const char* setName = uvNames[0].Buffer();
              FbxVector2 uv;
              bool unmapped = false;
              if (pMesh->GetPolygonVertexUV(iPolygon, iVertex, setName, uv, unmapped)) {
                if (!unmapped) {
                  new_uva = Vector2(uv[0], uv[1]);
                }
              }
              mi.texcoord0.push_back(new_uva);
            }

            if (uv_count >= 2) {
              const char* setName = uvNames[1].Buffer();
              FbxVector2 uv;
              bool unmapped = false;
              if (pMesh->GetPolygonVertexUV(iPolygon, iVertex, setName, uv, unmapped)) {
                if (!unmapped) {
                  new_uvb = Vector2(uv[0], uv[1]);
                }
              }
              mi.uvb.push_back(new_uvb);
            }

            // ----====---- Read Normals ----====----

            FbxVector4 new_normal;
            if (pMesh->GetPolygonVertexNormal(iPolygon, iVertex, new_normal)) {
              mi.normals.push_back(Vector3(new_normal[0], new_normal[1], new_normal[2]));
            }

            /*
             TODO - Tangent vectors imported from maya appear incorrectly...  Only calculating them in Kraken for now

            // ----====---- Read Tangents ----====----
            if(need_tangents) {
                for(int l = 0; l < tangent_count; ++l)
                {
                    FbxVector4 new_tangent;
                    FbxGeometryElementTangent* leTangent = pMesh->GetElementTangent(l);

                    if(leTangent->GetMappingMode() == FbxGeometryElement::eByPolygonVertex) {
                        switch (leTangent->GetReferenceMode()) {
                            case FbxGeometryElement::eDirect:
                                new_tangent = leTangent->GetDirectArray().GetAt(lControlPointIndex);
                                break;
                            case FbxGeometryElement::eIndexToDirect:
                            {
                                int id = leTangent->GetIndexArray().GetAt(lControlPointIndex);
                                new_tangent = leTangent->GetDirectArray().GetAt(id);
                            }
                                break;
                            default:
                                break; // other reference modes not shown here!
                        }
                    }
                    if(l == 0) {
                        mi.tangents.push_back(Vector3(new_tangent[0], new_tangent[1], new_tangent[2]));
                    }

                }
            }
            */

            source_vertex_id++;
            dest_vertex_id++;
            mat_vertex_count++;
          }
        }
      }
    }


    if (mat_vertex_count) {
      // ----====---- Output last material / submesh details ----====----
      mi.submesh_starts.push_back(mat_vertex_start);
      mi.submesh_lengths.push_back(mat_vertex_count);
      mi.material_names.push_back(pMaterial->GetName());
    }
  }

  delete control_point_weights;

  KRMesh* new_mesh = new KRMesh(context, pMesh->GetNode()->GetName());
  new_mesh->LoadData(mi, true, need_tangents);

  context.getMeshManager()->addMesh(new_mesh);
}

KRNode* LoadMesh(KRNode* parent_node, FbxScene* pFbxScene, FbxGeometryConverter* pGeometryConverter, FbxNode* pNode)
{
  std::string name = GetFbxObjectName(pNode);

  FbxMesh* pSourceMesh = (FbxMesh*)pNode->GetNodeAttribute();

  if (KRMesh::GetLODCoverage(pNode->GetName()) == 100) {
    // If this is the full detail model, add an instance of it to the scene file
    std::string light_map = pNode->GetName();
    light_map.append("_lightmap");

    // FINDME, HACK - Until we have a GUI, we're using prefixes to select correct object type
    const char* node_name = pNode->GetName();
    if (strncmp(node_name, "physics_collider_", strlen("physics_collider_")) == 0) {
      return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), KRAKEN_COLLIDER_PHYSICS, 0.0f);
    } else if (strncmp(node_name, "audio_collider_", strlen("audio_collider_")) == 0) {
      return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), KRAKEN_COLLIDER_AUDIO, 1.0f);
    } else if (strncmp(node_name, "collider", 8) == 0) { // 8 == strlen("collider")
        // Colliders can have a prefix of collider_##_, where ## indicates the layer mask
        // Colliders with a prefix of only collider_ will have a default layer mask of KRAKEN_COLLIDER_PHYSICS | KRAKEN_COLLIDER_AUDIO

        // Scan through the characters of the name until we no longer see digit characters (or see a '\0' indicating the end of the string)
      unsigned int layer = 0;
      const char* szNodeName = node_name + 8; // 8 == strlen("collider")
      const char* source_char = szNodeName;
      while (*source_char >= '0' && *source_char <= '9') {
        layer = layer * 10 + (*source_char++ - '0');
      }

      if (layer == 0) {
        // No layer mask number was found, use the default
        layer = KRAKEN_COLLIDER_PHYSICS | KRAKEN_COLLIDER_AUDIO;
      }
      if (*source_char == '_') {
        // Pattern has matched
        return new KRCollider(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), layer, 1.0f);
      } else {
        // This is just a normal node, which happened to be prefixed with "collider" but didn't have a number and underscore
        return new KRModel(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), light_map, 0.0f, true, false);
      }
    } else {
      return new KRModel(parent_node->getScene(), GetFbxObjectName(pNode), pSourceMesh->GetNode()->GetName(), light_map, 0.0f, true, false);
    }
  } else {
    return NULL;
  }

}

KRNode* LoadSkeleton(KRNode* parent_node, FbxScene* pFbxScene, FbxNode* pNode)
{
  std::string name = GetFbxObjectName(pNode);
  KRBone* new_bone = new KRBone(parent_node->getScene(), name.c_str());

  //static bool GetBindPoseContaining(FbxScene* pScene, FbxNode* pNode, PoseList& pPoseList, FbxArray<int>& pIndex);
//    PoseList pose_list;
//    FbxArray<int> pose_indices;
//    if(FbxPose::GetBindPoseContaining(pFbxScene, pNode, pose_list, pose_indices)) {
//        fprintf(stderr, "Found bind pose(s)!\n");
//    }

  return new_bone;
}

KRNode* LoadLocator(KRNode* parent_node, FbxScene* pFbxScene, FbxNode* pNode)
{
  std::string name = GetFbxObjectName(pNode);

  KRLocator* new_locator = new KRLocator(parent_node->getScene(), name.c_str());

  // Enumerate fbx properties so client code can convert locators into application-specific objects
  FbxProperty fbx_property = pNode->GetFirstProperty();
  while (fbx_property.IsValid()) {
    std::string property_name = fbx_property.GetNameAsCStr();
    std::transform(property_name.begin(), property_name.end(), property_name.begin(), ::tolower);

    boost::variant<int, double, bool, std::string> property_value = "";
    switch (fbx_property.GetPropertyDataType().GetType()) {
    case eFbxInt:
      new_locator->getUserAttributes()[property_name] = fbx_property.Get<FbxInt>();
      break;
    case eFbxDouble:
      new_locator->getUserAttributes()[property_name] = fbx_property.Get<FbxDouble>();
      break;
    case eFbxBool:
      new_locator->getUserAttributes()[property_name] = fbx_property.Get<FbxBool>();
      break;
    case eFbxFloat:
      new_locator->getUserAttributes()[property_name] = fbx_property.Get<FbxDouble>();
      break;
    case eFbxString:
      new_locator->getUserAttributes()[property_name] = std::string(fbx_property.Get<FbxString>().Buffer());
      break;
    default:
    {
      //     fprintf(stderr, "FBX property not imported due to unsupported data type: %s.%s\n", name.c_str(), property_name.c_str());
    }
    break;
    }

    fbx_property = pNode->GetNextProperty(fbx_property);
  }


  //static bool GetBindPoseContaining(FbxScene* pScene, FbxNode* pNode, PoseList& pPoseList, FbxArray<int>& pIndex);
  //    PoseList pose_list;
  //    FbxArray<int> pose_indices;
  //    if(FbxPose::GetBindPoseContaining(pFbxScene, pNode, pose_list, pose_indices)) {
  //        fprintf(stderr, "Found bind pose(s)!\n");
  //    }

  return new_locator;
}

KRNode* LoadCamera(KRNode* parent_node, FbxNode* pNode)
{
  FbxCamera* camera = (FbxCamera*)pNode->GetNodeAttribute();
  const char* szName = pNode->GetName();

  KRCamera* new_camera = new KRCamera(parent_node->getScene(), szName);
  return new_camera;
}

KRNode* LoadLight(KRNode* parent_node, FbxNode* pNode)
{
  const float PI = 3.14159265;
  const float d2r = PI * 2 / 360;

  FbxLight* pLight = (FbxLight*)pNode->GetNodeAttribute();
  const char* szName = pNode->GetName();

  FbxDouble3 light_color = pLight->Color.Get();
  FbxDouble light_intensity = pLight->Intensity.Get();
  FbxDouble light_hotspot = pLight->InnerAngle.Get(); // light inner cone angle (in degrees). Also know as the HotSpot
  FbxDouble light_coneangle = pLight->OuterAngle.Get(); // light outer cone angle (in degrees). Also known as the Falloff
  FbxLight::EDecayType light_decaytype = pLight->DecayType.Get(); // decay type
  FbxDouble light_decaystart = pLight->DecayStart.Get(); // decay start distance


//    KFbxLight::eNONE         - does not attenuate with distance
//    KFbxLight::eLINEAR       - attenuation of 1/d
//    KFbxLight::eQUADRATIC    - attenuation of 1/d^2
//    KFbxLight::eCUBIC        - attenuation of

  KRLight* new_light = NULL;

  switch (pLight->LightType.Get()) {
  case FbxLight::ePoint:
  {
    KRPointLight* l = new KRPointLight(parent_node->getScene(), szName);
    new_light = l;

  }
  break;
  case FbxLight::eDirectional:
  {
    KRDirectionalLight* l = new KRDirectionalLight(parent_node->getScene(), szName);
    new_light = l;
  }
  break;
  case FbxLight::eSpot:
  {
    KRSpotLight* l = new KRSpotLight(parent_node->getScene(), szName);
    l->setInnerAngle(light_hotspot * d2r);
    l->setOuterAngle(light_coneangle * d2r);
    new_light = l;
  }
  break;
  case FbxLight::eVolume:
  case FbxLight::eArea:
    // Not supported yet
    break;
  }

  if (new_light) {
    new_light->setColor(Vector3(light_color[0], light_color[1], light_color[2]));
    new_light->setIntensity(light_intensity);
    new_light->setDecayStart(light_decaystart);
  }
  return new_light;

}
