From 2fe8542114ca43aaea63b1d73ed03e00903106e6 Mon Sep 17 00:00:00 2001
From: Tazpn <tazpn@users.sourceforge.net>
Date: Sat, 1 Jul 2006 15:49:35 +0000
Subject: [PATCH] Miscellaneous fixes for Skin for DAoC and MW and trying not
 to breaking Civ4 and Ob.

---
 MaxNifPlugins_Readme.txt        | 28 ++++++++----
 NifImport/AppSettings.cpp       |  4 ++
 NifImport/AppSettings.h         |  4 ++
 NifImport/ImportAnimation.cpp   | 56 ++++++++++++++++--------
 NifImport/ImportMeshAndSkin.cpp | 23 +++++++---
 NifImport/ImportSkeleton.cpp    | 76 +++++++++++++++++++++++++++------
 NifImport/MaxNifImport.rc       |  4 +-
 NifImport/MaxNifImport.vcproj   |  2 +-
 NifImport/MaxNifTools.ini       | 34 +++++++++++++--
 NifImport/NIFImport.cpp         | 27 ++++++++++--
 NifImport/NIFImporter.h         |  6 +++
 NifImport/niutils.cpp           | 14 ++++++
 NifImport/niutils.h             | 32 ++++++++++----
 13 files changed, 245 insertions(+), 65 deletions(-)

diff --git a/MaxNifPlugins_Readme.txt b/MaxNifPlugins_Readme.txt
index 5df8cb0..be0812c 100644
--- a/MaxNifPlugins_Readme.txt
+++ b/MaxNifPlugins_Readme.txt
@@ -1,8 +1,7 @@
-						MaxPlugins 0.1.2
+						MaxPlugins 0.1.3
 						================
 
-  	This releases introduces an importer and fixes some bugs in the exporter.
-  
+ 
 	This plugin set currently consists of an exporter, importer and a utility 
  	plugin. This is a early release, so expect it to be buggy.  	
 	
@@ -26,12 +25,23 @@
 	choose "Reset XForm" and click "Reset Selected". This should fix it.
 
 
+	Change log
+	----------
 	
-	Changes since 0.1
-	-----------------
-  
-  	- Introduced the importer
-  
+	  0.1.3
+	  -----
+	  
+	o Importer
+	  - Fixed alignment issues when importing Morrowind Armor nifs
+	  - Added initial animation support (only for animations internal to nif, no kf file support yet)
+     - Fixed numerous issues with bone system (biped is still broken)
+     - Fixed issues with skin and doac nifs
+	  
+	  0.1.2
+	  -----
+	  
+	- Introduced the importer
+    
 	- Fixed collision generation, turned out that Oblivion
 	  doesn't like NvTriStrip's strips. Thanks to Razorwing
 	  for discovering the bug and Tanguy Fautré for his
@@ -47,7 +57,7 @@
     	3D Studio Max 6, 7 and 8
 	
   	Importer
-    	3d Studio Max 8, untested with previous releases
+    	3d Studio Max 8
 
 	Installation
 	------------
diff --git a/NifImport/AppSettings.cpp b/NifImport/AppSettings.cpp
index 6beb806..4c4240a 100644
--- a/NifImport/AppSettings.cpp
+++ b/NifImport/AppSettings.cpp
@@ -56,6 +56,10 @@ void AppSettings::ReadSettings(string iniFile)
    Skeleton = GetSetting<string>("Skeleton");
    useSkeleton = GetSetting<bool>("UseSkeleton", useSkeleton);
    goToSkeletonBindPosition = GetSetting<bool>("GoToSkeletonBindPosition", goToSkeletonBindPosition);
+   disableCreateNubsForBones = GetSetting<bool>("DisableCreateNubsForBones", disableCreateNubsForBones);
+   applyOverallTransformToSkinAndBones = GetSetting<int>("ApplyOverallTransformToSkinAndBones", -1);
+
+   dummyNodeMatches = TokenizeString(GetSetting<string>("DummyNodeMatches").c_str(), ";");
 }
 
 string AppSettings::FindImage(const string& fname){
diff --git a/NifImport/AppSettings.h b/NifImport/AppSettings.h
index 588cf97..52a7ea4 100644
--- a/NifImport/AppSettings.h
+++ b/NifImport/AppSettings.h
@@ -24,6 +24,7 @@ public:
       , parsedImages(false) 
       , useSkeleton(false)
       , goToSkeletonBindPosition(true)
+      , disableCreateNubsForBones(false)
    {}
 
    std::string Name;
@@ -36,8 +37,11 @@ public:
    std::string Skeleton;
    bool useSkeleton;
    bool goToSkeletonBindPosition;
+   bool disableCreateNubsForBones;
    NameValueCollection Environment;
    NameValueCollection imgTable;
+   stringlist dummyNodeMatches;
+   int applyOverallTransformToSkinAndBones;
 
    static void Initialize(Interface *gi);
    void ReadSettings(std::string iniFile);
diff --git a/NifImport/ImportAnimation.cpp b/NifImport/ImportAnimation.cpp
index 350578c..c239048 100644
--- a/NifImport/ImportAnimation.cpp
+++ b/NifImport/ImportAnimation.cpp
@@ -35,6 +35,8 @@ enum {
 };
 
 const float FramesPerSecond = 30.0f;
+const float FramesIncrement = 1.0f/30.0f;
+
 const int TicksPerFrame = GetTicksPerFrame();
 
 inline TimeValue TimeToFrame(float t) {
@@ -260,6 +262,9 @@ bool NifImporter::ImportAnimation()
    if (!enableAnimations)
       return false;
 
+   if (nodes.empty())
+      return false;
+
    AnimationImport ai(*this);
    return ai.AddValues(DynamicCast<NiObjectNET>(nodes[0]->GetChildren()));
 }
@@ -267,7 +272,6 @@ bool NifImporter::ImportAnimation()
 bool KFMImporter::ImportAnimation()
 {
    bool ok = false;
-   const float FramesIncrement = 1.0f/30.0f;
    int curFrame = 0;
    // Read Kf files
 #ifdef USE_UNSUPPORTED_CODE
@@ -367,10 +371,6 @@ bool AnimationImport::AddValues(NiObjectNETRef nref)
    if (NULL == c)
       return false;
 
- /*  vector<KeyTextValue> tkeys = BuildKeyValues(nref);
-   if (tkeys.empty())
-      return false;*/
-
    float time = 0.0f;
    list< NiTimeControllerRef > clist = nref->GetControllers();
    if (NiTransformControllerRef tc = SelectFirstObjectOfType<NiTransformController>(clist)) {
@@ -389,13 +389,33 @@ bool AnimationImport::AddValues(NiObjectNETRef nref)
 
 bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
 {
+   vector<Vector3Key> posKeys = data->GetTranslateKeys();
+   vector<QuatKey> quatKeys = data->GetQuatRotateKeys();
+   vector<FloatKey> sclKeys = data->GetScaleKeys();
+   vector<FloatKey> xKeys = data->GetXRotateKeys();
+   vector<FloatKey> yKeys = data->GetYRotateKeys();
+   vector<FloatKey> zKeys = data->GetZRotateKeys();
+
+   // Require more than one key to import (to avoid zero frame positioning used in mw and daoc
+   if (ni.requireMultipleKeys && 
+      !( posKeys.size() > 1
+      || quatKeys.size() > 1
+      || sclKeys.size() > 1
+      || xKeys.size() > 1
+      || yKeys.size() > 1
+      || zKeys.size() > 1
+      ))
+   {
+      return false;
+   }
+
    // Handle Translation
    switch (data->GetTranslateType())
    {
    case LINEAR_KEY:
       if (Control *subCtrl = MakePositionXYZ(c, Class_ID(LININTERP_FLOAT_CLASS_ID,0))) {
          vector<FloatKey> xkeys, ykeys, zkeys;
-         SplitKeys(data->GetTranslateKeys(), xkeys, ykeys, zkeys);
+         SplitKeys(posKeys, xkeys, ykeys, zkeys);
          SetKeys<ILinFloatKey, FloatKey>(subCtrl->GetXController(), xkeys, time);
          SetKeys<ILinFloatKey, FloatKey>(subCtrl->GetYController(), ykeys, time);
          SetKeys<ILinFloatKey, FloatKey>(subCtrl->GetZController(), zkeys, time);
@@ -406,7 +426,7 @@ bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
    case XYZ_ROTATION_KEY:
       if (Control *subCtrl = MakePositionXYZ(c, Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0))) {
          vector<FloatKey> xkeys, ykeys, zkeys;
-         SplitKeys(data->GetTranslateKeys(), xkeys, ykeys, zkeys);
+         SplitKeys(posKeys, xkeys, ykeys, zkeys);
          SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetXController(), xkeys, time);
          SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetYController(), ykeys, time);
          SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetZController(), zkeys, time);
@@ -416,7 +436,7 @@ bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
    case TBC_KEY:
       if (Control *subCtrl = MakePositionXYZ(c, Class_ID(TCBINTERP_FLOAT_CLASS_ID,0))) {
          vector<FloatKey> xkeys, ykeys, zkeys;
-         SplitKeys(data->GetTranslateKeys(), xkeys, ykeys, zkeys);
+         SplitKeys(posKeys, xkeys, ykeys, zkeys);
          SetKeys<ITCBFloatKey, FloatKey>(subCtrl->GetXController(), xkeys, time);
          SetKeys<ITCBFloatKey, FloatKey>(subCtrl->GetYController(), ykeys, time);
          SetKeys<ITCBFloatKey, FloatKey>(subCtrl->GetZController(), zkeys, time);
@@ -429,21 +449,21 @@ bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
    {
    case LINEAR_KEY:
       if (Control *subCtrl = MakeRotation(c, Class_ID(LININTERP_ROTATION_CLASS_ID,0), Class_ID(LININTERP_FLOAT_CLASS_ID,0))) {
-         SetKeys<ILinRotKey, QuatKey>(subCtrl, data->GetQuatRotateKeys(), time);
+         SetKeys<ILinRotKey, QuatKey>(subCtrl, quatKeys, time);
       }
       break;
 
    case QUADRATIC_KEY:
       if (Control *subCtrl = MakeRotation(c, Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID,0), Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0))) {
-         SetKeys<IBezQuatKey, QuatKey>(subCtrl, data->GetQuatRotateKeys(), time);
+         SetKeys<IBezQuatKey, QuatKey>(subCtrl, quatKeys, time);
       }
       break;
 
    case XYZ_ROTATION_KEY:
       if (Control *subCtrl = MakeRotation(c, Class_ID(EULER_CONTROL_CLASS_ID,0), Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0))) {
-         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetXController(), data->GetXRotateKeys(), time);
-         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetYController(), data->GetYRotateKeys(), time);
-         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetZController(), data->GetZRotateKeys(), time);
+         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetXController(), xKeys, time);
+         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetYController(), yKeys, time);
+         SetKeys<IBezFloatKey, FloatKey>(subCtrl->GetZController(), zKeys, time);
       }
       break;
 
@@ -451,11 +471,11 @@ bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
       if (ni.replaceTCBRotationWithBezier) {
          // TCB simply is not working for me.  Better off with Bezier as a workaround
          if (Control *subCtrl = MakeRotation(c, Class_ID(HYBRIDINTERP_ROTATION_CLASS_ID,0), Class_ID(HYBRIDINTERP_FLOAT_CLASS_ID,0))) {
-            SetKeys<IBezQuatKey, QuatKey>(subCtrl, data->GetQuatRotateKeys(), time);
+            SetKeys<IBezQuatKey, QuatKey>(subCtrl, quatKeys, time);
          }
       } else {
          if (Control *subCtrl = MakeRotation(c, Class_ID(TCBINTERP_ROTATION_CLASS_ID,0), Class_ID(TCBINTERP_FLOAT_CLASS_ID,0))) {
-            SetKeys<ITCBRotKey, QuatKey>(subCtrl, data->GetQuatRotateKeys(), time);
+            SetKeys<ITCBRotKey, QuatKey>(subCtrl, quatKeys, time);
          }
       }
       break;
@@ -465,18 +485,18 @@ bool AnimationImport::AddValues(Control *c, NiKeyframeDataRef data, float time)
    {
    case LINEAR_KEY:
       if (Control *subCtrl = MakeScale(c, Class_ID(LININTERP_SCALE_CLASS_ID,0))) {
-         SetKeys<ILinScaleKey, FloatKey>(subCtrl, data->GetScaleKeys(), time);
+         SetKeys<ILinScaleKey, FloatKey>(subCtrl, sclKeys, time);
       }
       break;
    case QUADRATIC_KEY:
    case XYZ_ROTATION_KEY:
       if (Control *subCtrl = MakeScale(c, Class_ID(HYBRIDINTERP_SCALE_CLASS_ID,0))) {
-         SetKeys<IBezScaleKey, FloatKey>(subCtrl, data->GetScaleKeys(), time);
+         SetKeys<IBezScaleKey, FloatKey>(subCtrl, sclKeys, time);
       }
       break;
    case TBC_KEY:
       if (Control *subCtrl = MakeScale(c, Class_ID(TCBINTERP_SCALE_CLASS_ID,0))) {
-         SetKeys<ITCBScaleKey, FloatKey>(subCtrl, data->GetScaleKeys(), time);
+         SetKeys<ITCBScaleKey, FloatKey>(subCtrl, sclKeys, time);
       }
       break;
    }
diff --git a/NifImport/ImportMeshAndSkin.cpp b/NifImport/ImportMeshAndSkin.cpp
index 28f9bcf..5083e01 100644
--- a/NifImport/ImportMeshAndSkin.cpp
+++ b/NifImport/ImportMeshAndSkin.cpp
@@ -311,6 +311,15 @@ bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom)
    if (ISkin *skin = (ISkin *) skinMod->GetInterface(I_SKIN)){
       ISkinImportData* iskinImport = (ISkinImportData*) skinMod->GetInterface(I_SKINIMPORTDATA);
 
+      Matrix3 m3;
+      if (applyOverallTransformToSkinAndBones) {
+         Matrix3 initNodeTM, initObjTM;
+         initNodeTM.IdentityMatrix(), initObjTM.IdentityMatrix();
+         skin->GetSkinInitTM(tnode, initNodeTM, false);
+         skin->GetSkinInitTM(tnode, initObjTM, true);
+         m3 = TOMATRIX3(data->GetOverallTransform());
+         iskinImport->SetSkinTm(tnode, initNodeTM * m3, initObjTM * m3);
+      }
       // Create Bone List
       Tab<INode*> bones;
       int i=0;
@@ -319,12 +328,16 @@ bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom)
          if (INode *boneRef = gi->GetINodeByName(name.c_str())) {
             bones.Append(1, &boneRef);
             iskinImport->AddBoneEx(boneRef, TRUE);
-         }
 
-         // Set Bone Transform
-         //Matrix3 tm = boneRef->GetObjectTM(0);
-         //Matrix3 m = TOMATRIX3(data->GetBoneTransform(i));
-         //iskinImport->SetBoneTm(boneRef, tm, m);
+            // Set Bone Transform
+            if (applyOverallTransformToSkinAndBones) {
+               Matrix3 initNodeTM, initBoneTM;
+               initNodeTM.IdentityMatrix(), initBoneTM.IdentityMatrix();
+               skin->GetBoneInitTM(boneRef, initNodeTM, false);
+               skin->GetBoneInitTM(boneRef, initBoneTM, true);
+               iskinImport->SetBoneTm(boneRef, initNodeTM * m3, initBoneTM * m3);
+            }
+         }
       }
       ObjectState os = tnode->EvalWorldState(0);
 
diff --git a/NifImport/ImportSkeleton.cpp b/NifImport/ImportSkeleton.cpp
index 9268019..54bc729 100644
--- a/NifImport/ImportSkeleton.cpp
+++ b/NifImport/ImportSkeleton.cpp
@@ -15,6 +15,9 @@ HISTORY:
 #include <obj/NiTriBasedGeom.h>
 #include <obj/NiTriBasedGeomData.h>
 #include <obj/NiTimeController.h>
+#include <obj/NiMultiTargetTransformController.h>
+#include <obj/NiStringExtraData.h>
+#include <obj/NiBillboardNode.h>
 #include <float.h>
 #include <dummy.h>
 
@@ -316,7 +319,6 @@ INode *NifImporter::CreateBone(const string& name, Point3 startPos, Point3 endPo
 
 INode *NifImporter::CreateHelper(const string& name, Point3 startPos)
 {
-   //POINTHELP_CLASS_ID
    if (DummyObject *ob = (DummyObject *)gi->CreateInstance(HELPER_CLASS_ID,Class_ID(DUMMY_CLASS_ID,0))) {
       const float DUMSZ = 1.0f;
       ob->SetBox(Box3(Point3(-DUMSZ,-DUMSZ,-DUMSZ),Point3(DUMSZ,DUMSZ,DUMSZ)));
@@ -327,6 +329,14 @@ INode *NifImporter::CreateHelper(const string& name, Point3 startPos)
          return n;
       }
    }
+   //if (Object *ob = (Object *)gi->CreateInstance(HELPER_CLASS_ID,Class_ID(BONE_CLASS_ID,0))) {
+   //   if (INode *n = gi->CreateObjectNode(ob)) {
+   //      n->SetName(const_cast<TCHAR*>(name.c_str()));
+   //      Quat q; q.Identity();
+   //      PosRotScaleNode(n, startPos, q, 1.0f, prsPos);
+   //      return n;
+   //   }
+   //}
    return  NULL;
 }
 
@@ -350,10 +360,52 @@ float GetObjectLength(NiAVObjectRef obj)
    return clen;
 }
 
+static void BuildControllerRefList(NiNodeRef node, map<string,int>& ctrlCount)
+{
+   list<NiTimeControllerRef> ctrls = node->GetControllers();
+   for (list<NiTimeControllerRef>::iterator itr = ctrls.begin(), end = ctrls.end(); itr != end; ++itr) {
+      list<NiNodeRef> nlist = DynamicCast<NiNode>((*itr)->GetRefs());
+
+      // Append extra targets.  Goes away if GetRefs eventually returns the extra targets
+      if (NiMultiTargetTransformControllerRef multiCtrl = DynamicCast<NiMultiTargetTransformController>(*itr)) {
+         vector<NiNodeRef> extra = multiCtrl->GetExtraTargets();
+         nlist.insert(nlist.end(), extra.begin(), extra.end());
+      }
+
+      for (list<NiNodeRef>::iterator nitr = nlist.begin(); nitr != nlist.end(); ++nitr){
+         string name = (*nitr)->GetName();
+         map<string,int>::iterator citr = ctrlCount.find(name);
+         if (citr != ctrlCount.end())
+            ++(*citr).second;
+         else
+            ctrlCount[name] = 1;
+      }
+   }
+}
+
+static bool HasControllerRef(map<string,int>& ctrlCount, const string& name)
+{
+   return (ctrlCount.find(name) != ctrlCount.end());
+}
+
+static bool HasUserPropBuffer(NiNodeRef node)
+{
+   if (node) {
+      if (NiStringExtraDataRef data = SelectFirstObjectOfType<NiStringExtraData>(node->GetExtraData())){
+         if (strmatch(data->GetName(), "UserPropBuffer"))
+            return true;
+      }
+   }
+   return false;
+}
+
 void NifImporter::ImportBones(NiNodeRef node)
 {
    try 
    {
+      if (uncontrolledDummies)
+         BuildControllerRefList(node, ctrlCount);
+
       string name = node->GetName();
       vector<NiAVObjectRef> children = node->GetChildren();
       vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);
@@ -404,22 +456,19 @@ void NifImporter::ImportBones(NiNodeRef node)
       }
       else
       {
-         list<NiTimeControllerRef> ctrls = node->GetControllers();
-         if (parent == NULL || parent->GetParent() == NULL || ctrls.empty())
-         {
+         bool isDummy = ( (uncontrolledDummies && !HasControllerRef(ctrlCount, name))
+                     || (!dummyNodeMatches.empty() && wildmatch(dummyNodeMatches, name))
+                     || (convertBillboardsToDummyNodes && node->IsDerivedType(NiBillboardNode::TypeConst()))
+                      );
+         if (isDummy && createNubsForBones)
             bone = CreateHelper(name, p);
-            if (ctrls.empty())
-               bone->Hide(TRUE);
-         }
          else if (bone = CreateBone(name, p, pp, zAxis))
          {
             PosRotScaleNode(bone, p, q, scale, prs);
-            if (createNubsForBones && childNodes.empty()){
-               if (INode *helper = CreateHelper(string().assign(name).append(" Nub"), pp)){
-                  helper->Hide(TRUE);
-                  bone->AttachChild(helper, 1);
-               }
-            }
+            if (isDummy)
+               bone->Hide(TRUE);
+            else
+               bone->Hide(node->GetHidden() ? TRUE : FALSE);
          }
          if (bone)
          {
@@ -428,7 +477,6 @@ void NifImporter::ImportBones(NiNodeRef node)
                if (INode *pn = gi->GetINodeByName(parent->GetName().c_str()))
                   pn->AttachChild(bone, 1);
             }
-            bone->Hide(node->GetHidden() ? TRUE : FALSE);
          }
       }
       if (bone)
diff --git a/NifImport/MaxNifImport.rc b/NifImport/MaxNifImport.rc
index d181dc0..51d8121 100644
--- a/NifImport/MaxNifImport.rc
+++ b/NifImport/MaxNifImport.rc
@@ -88,7 +88,7 @@ END
 //
 
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 0,1,3,2
+ FILEVERSION 0,1,3,4
  PRODUCTVERSION 0,1,3,0
  FILEFLAGSMASK 0x37L
 #ifdef _DEBUG
@@ -105,7 +105,7 @@ BEGIN
         BLOCK "040904b0"
         BEGIN
             VALUE "FileDescription", "3ds Max Nif Importer"
-            VALUE "FileVersion", "0, 1, 3, 2"
+            VALUE "FileVersion", "0, 1, 3, 4"
             VALUE "InternalName", "MaxNifImport.dli"
             VALUE "LegalCopyright", "Copyright (c) 2006, NIF File Format Library and Tools\r\nAll rights reserved."
             VALUE "OriginalFilename", "MaxNifImport.dli"
diff --git a/NifImport/MaxNifImport.vcproj b/NifImport/MaxNifImport.vcproj
index 8ef3a13..561006e 100644
--- a/NifImport/MaxNifImport.vcproj
+++ b/NifImport/MaxNifImport.vcproj
@@ -49,7 +49,7 @@
 				Name="VCCLCompilerTool"
 				AdditionalOptions="/LD "
 				InlineFunctionExpansion="1"
-				AdditionalIncludeDirectories="C:\3dsmax8\maxsdk\include"
+				AdditionalIncludeDirectories=""
 				PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;_CRT_SECURE_NO_DEPRECATE;USE_NIFLIB_TEMPLATE_HELPERS;_USE_MATH_DEFINES"
 				StringPooling="true"
 				ExceptionHandling="2"
diff --git a/NifImport/MaxNifTools.ini b/NifImport/MaxNifTools.ini
index c2b63fb..b40bf05 100644
--- a/NifImport/MaxNifTools.ini
+++ b/NifImport/MaxNifTools.ini
@@ -6,7 +6,7 @@
 ShortDescription=Netimmerse/Gamebryo
 ; KnownApplications - Used to indicate which sections in the ini file point 
 ;    to "Applications" which have their own settings in a section below.
-KnownApplications=Oblivion;Morrowind;Civ4
+KnownApplications=Oblivion;Morrowind;Civ4;DAoC
 ; Reparse the Applications (and therefore Texture directory cache) on every import/export
 Reparse=0
 
@@ -53,14 +53,25 @@ ForceRotation=0
 BrowseForSkeleton=1
 ; DefaultName for Skeletons (use if in same directory as imported nif)
 DefaultSkeletonName=skeleton.nif
-; Create Nubs for final bone in chain (not supported yet)
-CreateNubsForBones=1
+
+; Create Dummy nodes for bones that appear to be helper objects. Default: 0
+CreateNubsForBones=0
+; Dummy nodes wildcard matching. (Hide these when not created as Dummy) Default: Bip??;Bip* NonAccum
+DummyNodeMatches=Bip??;* NonAccum
+; Make Billboard nodes to Dummy nodes rather than bones. Default: 1
+ConvertBillboardsToDummyNodes=1
+; Add Bones not controlled by a controller as dummy. Default: 1
+UncontrolledDummies=1
 
 [AnimationImport]
 ; Enable Animation Import.  Default: 1
 EnableAnimations=1
+; Require Multiple Keys to be present to before importing animation. (Kludge to workaround DOaC issues.) Default: 1
+RequireMultipleKeys=1
 ; Replace TCB Rotation with Bezier (workaround for unexpected behavior with TCB rotations)
 ReplaceTCBRotationWithBezier=1
+; Apply the overall transform to skin and bones. Default: 0
+ApplyOverallTransformToSkinAndBones=0
 
 ; [Applications]
 ; RootPaths - Semicolon separated list of base directories to use when determining which app the imported file is from
@@ -88,6 +99,7 @@ TextureRootPaths=${RootPath};${TextureRootPath}
 TextureExtensions=.dds;
 TextureSearchPaths=${RootPath}\Textures;${TextureRootPath}\Textures\Characters;${TextureRootPath}\Textures\Armor
 GoToSkeletonBindPosition=1
+ApplyOverallTransformToSkinAndBones=0
 
 [Morrowind]
 InstallPath=[HKLM\SOFTWARE\Bethesda Softworks\Morrowind]=@"Installed Path"
@@ -98,6 +110,7 @@ TextureRootPaths=${RootPath}\Textures;${ExtractFolder}\Textures
 TextureExtensions=.tga;
 TextureSearchPaths=${RootPath}\Textures
 GoToSkeletonBindPosition=1
+ApplyOverallTransformToSkinAndBones=1
 
 [Civ4]
 InstallPath=[HKEY_LOCAL_MACHINE\SOFTWARE\Firaxis Games\Sid Meier's Civilization 4]=@"INSTALLDIR"
@@ -107,4 +120,17 @@ RootPaths=${ExtractFolder};${InstallPath}\Assets;${InstallPath}\Mods;%USERPROFIL
 TextureRootPaths=$(ExtractFolder)\art\shared\
 TextureExtensions=.dds;.bmp
 TextureSearchPaths=
-GoToSkeletonBindPosition=1
\ No newline at end of file
+GoToSkeletonBindPosition=1
+DummyNodeMatches=MD;Bip;Bip??;* NonAccum;Effect*;Sound*;Dummy*
+ApplyOverallTransformToSkinAndBones=0
+
+[DAoC]
+Isles_InstallPath=[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Dark Age of Camelot - Shrouded Isles_is1]=@"InstallLocation"
+RootPath=
+ExtractFolder=
+RootPaths=${Isles_InstallPath};${ExtractFolder}
+TextureRootPaths=$(ExtractFolder)
+TextureExtensions=.dds;.bmp;.tga
+TextureSearchPaths=
+GoToSkeletonBindPosition=1
+ApplyOverallTransformToSkinAndBones=1
diff --git a/NifImport/NIFImport.cpp b/NifImport/NIFImport.cpp
index ee9a21b..c9dc00e 100644
--- a/NifImport/NIFImport.cpp
+++ b/NifImport/NIFImport.cpp
@@ -46,8 +46,8 @@ static void BuildNodes(NiNodeRef object, vector<NiNodeRef>& nodes)
 {
    if (!object)
       return;
+   nodes.push_back(object);
    vector<NiNodeRef> links = DynamicCast<NiNode>(object->GetChildren());
-   nodes.insert(nodes.end(), links.begin(), links.end());
    for (vector<NiNodeRef>::iterator itr = links.begin(), end = links.end(); itr != end; ++itr)
       BuildNodes(*itr, nodes);
 }
@@ -136,11 +136,26 @@ void NifImporter::LoadIniSettings()
    maxBoneWidth = GetIniValue<float>(BipedImportSection, "MaxBoneWidth", 3.0f);
    boneWidthToLengthRatio = GetIniValue<float>(BipedImportSection, "BoneWidthToLengthRatio", 0.25f);
    createNubsForBones = GetIniValue<bool>(BipedImportSection, "CreateNubsForBones", true);
+   dummyNodeMatches = TokenizeString(GetIniValue<string>(BipedImportSection, "DummyNodeMatches", "").c_str(), ";");
+   convertBillboardsToDummyNodes = GetIniValue<bool>(BipedImportSection, "ConvertBillboardsToDummyNodes", true);
+   uncontrolledDummies = GetIniValue<bool>(BipedImportSection, "UncontrolledDummies", true);
 
    replaceTCBRotationWithBezier = GetIniValue<bool>(AnimImportSection, "ReplaceTCBRotationWithBezier", true);
    enableAnimations = GetIniValue<bool>(AnimImportSection, "EnableAnimations", true);
-
-   goToSkeletonBindPosition = (appSettings ? appSettings->goToSkeletonBindPosition : false);
+   requireMultipleKeys = GetIniValue<bool>(AnimImportSection, "RequireMultipleKeys", true);
+   applyOverallTransformToSkinAndBones = GetIniValue<bool>(AnimImportSection, "ApplyOverallTransformToSkinAndBones", true);
+
+   goToSkeletonBindPosition = false;
+   // Override specific settings
+   if (appSettings) {
+      if (appSettings->disableCreateNubsForBones)
+         createNubsForBones = false;
+      goToSkeletonBindPosition = appSettings->goToSkeletonBindPosition;
+      if (!appSettings->dummyNodeMatches.empty())
+         dummyNodeMatches = appSettings->dummyNodeMatches;
+      if (appSettings->applyOverallTransformToSkinAndBones != -1)
+         applyOverallTransformToSkinAndBones = appSettings->applyOverallTransformToSkinAndBones ? true : false;
+   }
 }
 
 void NifImporter::SaveIniSettings()
@@ -229,7 +244,11 @@ bool NifImporter::DoImport()
       }
 
       if (isValid()) {
-         ImportBones(DynamicCast<NiNode>(rootNode->GetChildren()));
+         if (strmatch(rootNode->GetName(), "Scene Root"))
+            ImportBones(DynamicCast<NiNode>(rootNode->GetChildren()));
+         else
+            ImportBones(rootNode);
+
          ok = ImportMeshes(rootNode);
 
          if (importSkeleton && removeUnusedImportedBones){
diff --git a/NifImport/NIFImporter.h b/NifImport/NIFImporter.h
index 2bec037..e570592 100644
--- a/NifImport/NIFImporter.h
+++ b/NifImport/NIFImporter.h
@@ -48,13 +48,19 @@ public:
    float maxBoneWidth;
    float boneWidthToLengthRatio;
    bool createNubsForBones;
+   stringlist dummyNodeMatches;
+   bool convertBillboardsToDummyNodes;
+   bool uncontrolledDummies;
 
    // Animation related Settings
    bool replaceTCBRotationWithBezier;
    bool enableAnimations;
+   bool requireMultipleKeys;
+   bool applyOverallTransformToSkinAndBones;
 
    vector<Niflib::NiObjectRef> blocks;
    vector<Niflib::NiNodeRef> nodes;
+   map<string,int> ctrlCount; // counter for number of controllers referencing a node
 
    NifImporter(const TCHAR *Name,ImpInterface *I,Interface *GI, BOOL SuppressPrompts);
    virtual void Initialize();
diff --git a/NifImport/niutils.cpp b/NifImport/niutils.cpp
index 8b94298..5c7cc39 100644
--- a/NifImport/niutils.cpp
+++ b/NifImport/niutils.cpp
@@ -299,6 +299,20 @@ int wildcmpi(const TCHAR *wild, const TCHAR *string) {
    return !*wild;
 }
 
+bool wildmatch(const string& match, const std::string& value) 
+{
+   return (wildcmpi(match.c_str(), value.c_str())) ? true : false;
+}
+
+bool wildmatch(const stringlist& matches, const std::string& value)
+{
+   for (stringlist::const_iterator itr=matches.begin(), end=matches.end(); itr != end; ++itr){
+      if (wildcmpi((*itr).c_str(), value.c_str()))
+         return true;
+   }
+   return false;
+}
+
 //! Renames Max Node if it exists
 void RenameNode(Interface *gi, LPCTSTR SrcName, LPCTSTR DstName)
 {
diff --git a/NifImport/niutils.h b/NifImport/niutils.h
index b52b713..1035cc6 100644
--- a/NifImport/niutils.h
+++ b/NifImport/niutils.h
@@ -41,9 +41,6 @@ INFO: See Implementation for minimalist comments
 #define _countof(x) (sizeof(x)/sizeof((x)[0]))
 #endif
 
-extern int wildcmp(const TCHAR *wild, const TCHAR *string);
-extern int wildcmpi(const TCHAR *wild, const TCHAR *string);
-
 // Trim whitespace before and after a string
 inline TCHAR *Trim(TCHAR*&p) { 
    while(_istspace(*p)) *p++ = 0; 
@@ -114,6 +111,30 @@ struct NumericStringEquivalence
    }
 };
 
+// Common collections that I use
+typedef std::map<std::string, std::string, ltstr> NameValueCollection;
+typedef std::pair<std::string, std::string> KeyValuePair;
+typedef std::list<std::string> stringlist;
+
+extern int wildcmp(const TCHAR *wild, const TCHAR *string);
+extern int wildcmpi(const TCHAR *wild, const TCHAR *string);
+
+inline bool strmatch(const string& lhs, const std::string& rhs) {
+   return (0 == _tcsicmp(lhs.c_str(), rhs.c_str()));
+}
+inline bool strmatch(const TCHAR* lhs, const std::string& rhs) {
+   return (0 == _tcsicmp(lhs, rhs.c_str()));
+}
+inline bool strmatch(const string& lhs, const TCHAR* rhs) {
+   return (0 == _tcsicmp(lhs.c_str(), rhs));
+}
+inline bool strmatch(const TCHAR* lhs, const TCHAR* rhs) {
+   return (0 == _tcsicmp(lhs, rhs));
+}
+
+bool wildmatch(const string& match, const std::string& value);
+bool wildmatch(const stringlist& matches, const std::string& value);
+
 // Generic IniFile reading routine
 template<typename T>
 inline T GetIniValue(LPCTSTR Section, LPCTSTR Setting, T Default, LPCTSTR iniFileName){
@@ -178,11 +199,6 @@ inline void SetIniValue<TSTR>(LPCTSTR Section, LPCTSTR Setting, TSTR value, LPCT
    WritePrivateProfileString(Section, Setting, value.data(), iniFileName);
 }
 
-// Common collections that I use
-typedef std::map<std::string, std::string, ltstr> NameValueCollection;
-typedef std::pair<std::string, std::string> KeyValuePair;
-typedef std::list<std::string> stringlist;
-
 extern TSTR FormatText(const TCHAR* format,...);
 extern std::string FormatString(const TCHAR* format,...);
 
-- 
GitLab