From 8c3c70496dfbf0576d57a2c37937dd783b14105f Mon Sep 17 00:00:00 2001 From: Tazpn <tazpn@users.sourceforge.net> Date: Sun, 2 Jul 2006 13:53:41 +0000 Subject: [PATCH] Fix skin rotations once again. Still having issues with Civ4 and DAoC not quite lining up. --- NifImport/ImportMeshAndSkin.cpp | 16 +- NifImport/ImportSkeleton.cpp | 417 +++++++++++++++++++++----------- NifImport/MaxNifTools.ini | 4 +- NifImport/NIFImporter.h | 5 +- 4 files changed, 281 insertions(+), 161 deletions(-) diff --git a/NifImport/ImportMeshAndSkin.cpp b/NifImport/ImportMeshAndSkin.cpp index 5287ba3..6ae097e 100644 --- a/NifImport/ImportMeshAndSkin.cpp +++ b/NifImport/ImportMeshAndSkin.cpp @@ -312,12 +312,9 @@ bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom) if (ISkin *skin = (ISkin *) skinMod->GetInterface(I_SKIN)){ ISkinImportData* iskinImport = (ISkinImportData*) skinMod->GetInterface(I_SKINIMPORTDATA); - Matrix3 m3(TRUE); - if (applyOverallTransformToSkinAndBones) { - m3 = TOMATRIX3(data->GetOverallTransform()); - Matrix3 im3 = Inverse(m3); - iskinImport->SetSkinTm(tnode, im3, im3); // ??? - } + Matrix3 m3 = TOMATRIX3(data->GetOverallTransform()); + Matrix3 im3 = Inverse(m3); + iskinImport->SetSkinTm(tnode, m3, m3); // ??? // Create Bone List Tab<INode*> bones; for (size_t i=0; i<nifBones.size(); ++i){ @@ -331,9 +328,10 @@ bool NifImporter::ImportSkin(ImpNode *node, NiTriBasedGeomRef triGeom) iskinImport->AddBoneEx(boneRef, TRUE); // Set Bone Transform - if (applyOverallTransformToSkinAndBones) { - iskinImport->SetBoneTm(boneRef, ib3, ib3); - } + Matrix3 tm = ib3; + if (applyOverallTransformToSkinAndBones) + ib3 *= im3; + iskinImport->SetBoneTm(boneRef, ib3, ib3); } } ObjectState os = tnode->EvalWorldState(0); diff --git a/NifImport/ImportSkeleton.cpp b/NifImport/ImportSkeleton.cpp index 54bc729..b2d0110 100644 --- a/NifImport/ImportSkeleton.cpp +++ b/NifImport/ImportSkeleton.cpp @@ -12,6 +12,7 @@ HISTORY: **********************************************************************/ #include "stdafx.h" #include "MaxNifImport.h" +//#include <cs/Biped8Api.h> #include <obj/NiTriBasedGeom.h> #include <obj/NiTriBasedGeomData.h> #include <obj/NiTimeController.h> @@ -41,6 +42,43 @@ void GoToSkeletonBindPosition(vector<NiNodeRef>& blocks) } } +float GetObjectLength(NiAVObjectRef obj) +{ + float clen = obj->GetLocalTranslation().Magnitude(); + if (clen < (FLT_EPSILON*10)) { + if (NiTriBasedGeomRef geom = DynamicCast<NiTriBasedGeom>(obj)) { + if (NiTriBasedGeomDataRef data = geom->GetData()) { + clen = data->GetRadius() * 2.0f; + } + } + } + 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; + } + } +} + + bool NifImporter::HasSkeleton() { if (!skeletonCheck.empty()){ @@ -54,9 +92,12 @@ bool NifImporter::HasSkeleton() bool NifImporter::IsBiped() { if (hasSkeleton){ - list<NiExtraDataRef> extraData = nodes[0]->GetExtraData(); - if (!extraData.empty()) { - return ( DynamicCast<BSBound>(extraData).size() != 0 ); + NiNodeRef rootNode = root; + if (rootNode){ + list<NiExtraDataRef> extraData = rootNode->GetExtraData(); + if (!extraData.empty()) { + return ( SelectFirstObjectOfType<BSBound>(extraData) != NULL ); + } } } return false; @@ -115,25 +156,33 @@ void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes) numfingers, nfinglinks, numtoes, ntoelinks, bipedAnkleAttach, prop1exists, prop2exists, prop3exists, forearmTwistLinks, upperarmTwistLinks, thighTwistLinks, calfTwistLinks, horseTwistLinks); - master->SetRootName(const_cast<TCHAR*>(bipname.c_str())); if (master) { + master->SetRootName(const_cast<TCHAR*>(bipname.c_str())); + + //if (INode *rootNode = gi->GetINodeByName(bipname.c_str())) { + // if (Control *c = rootNode->GetTMController()) { + // if (IBipMaster8 *master8 = GetBipMaster8Interface(c)) { + // bool ok = master8->SetEulerActive(KEY_LARM, EULERTYPE_XYZ); + // ok = ok; + // } + // } + //} master->BeginModes(BMODE_FIGURE, FALSE); master->SetTrianglePelvis(FALSE); master->SetDisplaySettings(BDISP_BONES); LPCTSTR bipname = master->GetRootName(); - // Rename twists, if necessary + // Rename twists, if necessary for Oblivion RenameNode(gi, FormatText("%s L ForeTwist", bipname), FormatText("%s L ForearmTwist", bipname)); RenameNode(gi, FormatText("%s R ForeTwist", bipname), FormatText("%s R ForearmTwist", bipname)); RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s L UpperArmTwist", bipname)); RenameNode(gi, FormatText("%s R LUpArmTwist", bipname), FormatText("%s R UpperArmTwist", bipname)); - NiNodeRef nifBip = FindNodeByName(nodes, bipname); - //AlignBiped(master, nifBip); - ScaleBiped(master, nifBip, true); - PositionBiped(master, nifBip, true); - RotateBiped(master, nifBip, true); + if (NiNodeRef nifBip = FindNodeByName(nodes, bipname)) + { + AlignBiped(master, nifBip); + } } } } @@ -148,146 +197,257 @@ void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes) master->EndModes(BMODE_FIGURE, TRUE); } -void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef block) +static float CalcLength(NiNodeRef node, vector<NiAVObjectRef>& children) { - string name = block->GetName(); - Matrix44 wt = block->GetWorldTransform(); - Matrix44 lt = block->GetLocalTransform(); - - Vector3 pos; Matrix33 rot; float scale; - wt.Decompose(pos, rot, scale); - - INode *node = gi->GetINodeByName(name.c_str()); - if (node != NULL) - { - Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m - master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); - - Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3(0,0,0)); - Matrix3 im = Inverse(m); - - Point3 p; Quat q; Point3 s; - DecomposeMatrix(im, p, q, s); - master->SetBipedRot(q, 0, node, TRUE); + bool hasChildren = !children.empty(); + float len = 0.0f; + if (hasChildren) { + for (vector<NiAVObjectRef>::iterator itr=children.begin(), end = children.end(); itr != end; ++itr) { + len += GetObjectLength(*itr); + } + len /= float(children.size()); } else { - - } - vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); - for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ - AlignBiped(master, *itr); + len = node->GetLocalTranslation().Magnitude(); } + return len; } -void NifImporter::PositionBiped(IBipMaster* master, NiNodeRef block, bool Recurse) +Matrix3 GetLocalTM(INode *node) { - string name = block->GetName(); - - Matrix44 wt = block->GetWorldTransform(); - Matrix44 lt = block->GetLocalTransform(); - - Vector3 pos; Matrix33 rot; float scale; - wt.Decompose(pos, rot, scale); - - INode *node = gi->GetINodeByName(name.c_str()); - if (node != NULL) + if (INode *parent = node->GetParentNode()) { - Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m - master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); - - Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3(0,0,0)); - Matrix3 im = Inverse(m); - Point3 p; Quat q; Point3 s; - DecomposeMatrix(im, p, q, s); - master->SetBipedRot(q, 0, node, TRUE); + Matrix3 parentTM, nodeTM; + nodeTM = node->GetNodeTM(0); + parent = node->GetParentNode(); + parentTM = parent->GetNodeTM(0); + return nodeTM*Inverse(parentTM); } else { - + return node->GetNodeTM(0); } - if (Recurse) +} + +static float CalcLength(INode *bone) +{ + int n = bone->NumberOfChildren(); + float len = 0.0f; + if (n > 0) { - vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); - for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ - PositionBiped(master, *itr, Recurse); + Matrix3 m = bone->GetNodeTM(0); + Point3 p = m.GetTrans(); + for (int i = 0; i<n; i++) + { + INode *child = bone->GetChildNode(i); + Matrix3 cm = child->GetObjectTM(0); + Point3 cp = cm.GetTrans(); + + float clen = Length(p-cp); + len += clen; } + len /= float(n); } + else + { + len = Length(GetLocalTM(bone).GetTrans()); + } + return len; } -void NifImporter::RotateBiped(IBipMaster* master, NiNodeRef block, bool Recurse) +static bool HasBipedPosDOF(LPCTSTR name) { - string name = block->GetName(); - - Matrix44 wt = block->GetWorldTransform(); - Matrix44 lt = block->GetLocalTransform(); - - Vector3 pos; Matrix33 rot; float scale; - wt.Decompose(pos, rot, scale); + // Check for specific nodes to ignore. + // These are nodes which have a full translation DOF and + // there for do not effect the scale value we are toying with + return ( wildcmpi("Bip?? ? Clavicle", name) + || wildcmpi("Bip?? ? Toe?", name) + || wildcmpi("Bip?? ? Finger?", name) + || wildcmpi("Bip?? *Twist*", name) + ); +} - INode *node = gi->GetINodeByName(name.c_str()); - if (node != NULL) +static float CalcScale(INode *bone, NiNodeRef node, vector<NiNodeRef>& children) +{ + int n = bone->NumberOfChildren(); + if (n > 0) { - Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m - master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + float len1 = 0.0f; + float len2 = 0.0f; + Matrix3 m = bone->GetNodeTM(0); + Matrix3 m2 = TOMATRIX3(node->GetWorldTransform()); + for (int i = 0; i<n; i++) + { + INode *child = bone->GetChildNode(i); + LPCTSTR name = child->GetName(); + if (HasBipedPosDOF(name)) + continue; - Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3(0,0,0)); - Matrix3 im = Inverse(m); + Matrix3 cm = child->GetObjectTM(0); + len1 += Length(m.GetTrans()-cm.GetTrans()); - Point3 p; Quat q; Point3 s; - DecomposeMatrix(im, p, q, s); - master->SetBipedRot(q, 0, node, TRUE); + if (NiNodeRef child2 = FindNodeByName(children, string(child->GetName()))){ + Matrix3 cm2 = TOMATRIX3(child2->GetWorldTransform()); + len2 += Length(m2.GetTrans()-cm2.GetTrans()); + } + } + return (len2 != 0.0f && len1 != 0.0f) ? (len2/len1) : 1.0f; } - else - { + return 1.0f; +} - } - if (Recurse) +void PosRotScaleBiped(IBipMaster* master, INode *n, Point3 p, Quat& q, float s, PosRotScale prs, TimeValue t = 0) +{ + if (prs & prsScale) + master->SetBipedScale(TRUE, ScaleValue(Point3(s,s,s)), t, n); + if (prs & prsRot) + master->SetBipedRot(q, t, n, FALSE); + if (prs & prsPos) + master->SetBipedPos(p, t, n, FALSE); +} +AngAxis CalcAngAxis(Point3 vs, Point3 vf) +{ + Point3 cross = CrossProd(vs, vf); + // Test for colinear + if (cross.x < 0.01 && cross.y < 0.01 && cross.z < 0.01) + return AngAxis(Matrix3(TRUE)); + float dot = DotProd(vs, vf); + return AngAxis( cross, acos( dot ) ); +} +Matrix3 GenerateRotMatrix(AngAxis a) +{ + Matrix3 m(TRUE); + float u = a.axis.x; + float v = a.axis.y; + float w = a.axis.z; + float rcos = cos(a.angle); + float rsin = sin(a.angle); + m[0][0] = rcos + u*u*(1-rcos); + m[1][0] = w * rsin + v*u*(1-rcos); + m[2][0] = -v * rsin + w*u*(1-rcos); + m[0][1] = -w * rsin + u*v*(1-rcos); + m[1][1] = rcos + v*v*(1-rcos); + m[2][1] = u * rsin + w*v*(1-rcos); + m[0][2] = v * rsin + u*w*(1-rcos); + m[1][2] = -u * rsin + v*w*(1-rcos); + m[2][2] = rcos + w*w*(1-rcos); + return m; +} +static AngAxis CalcTransform(INode *bone, NiNodeRef node, vector<NiNodeRef>& children) +{ + Matrix3 mr(TRUE); + int n = bone->NumberOfChildren(); + if (n > 0) { - vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); - for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ - RotateBiped(master, *itr, Recurse); + int c = 0; + Point3 vs(0.0f, 0.0f, 0.0f), vf(0.0f, 0.0f, 0.0f); + Matrix3 m = bone->GetNodeTM(0); + Matrix3 m2 = TOMATRIX3(node->GetWorldTransform()); + for (int i = 0; i<n; i++) + { + INode *child = bone->GetChildNode(i); + LPCTSTR name = child->GetName(); + if (HasBipedPosDOF(name)) + continue; + + Matrix3 cm = child->GetObjectTM(0); + vs += (m.GetTrans()-cm.GetTrans()); + + if (NiNodeRef child2 = FindNodeByName(children, string(child->GetName()))){ + Matrix3 cm2 = TOMATRIX3(child2->GetWorldTransform()); + vf += (m2.GetTrans()-cm2.GetTrans()); + } + ++c; } + vs = FNormalize(vs); + vf = FNormalize(vf); + Point3 cross = CrossProd(vs, vf); + if (fabs(cross.x) < 0.01 && fabs(cross.y) < 0.01 && fabs(cross.z) < 0.01) + return AngAxis(Point3(0.0f, 0.0f, 0.0f), 0.0f); + float dot = DotProd(vs, vf); + return AngAxis( cross, acos( dot ) ); } + return mr; } -void NifImporter::ScaleBiped(IBipMaster* master, NiNodeRef block, bool Recurse) -{ - string name = block->GetName(); - Matrix44 wt = block->GetWorldTransform(); - Matrix44 lt = block->GetLocalTransform(); - Vector3 pos; Matrix33 rot; float scale; - wt.Decompose(pos, rot, scale); - - INode *node = gi->GetINodeByName(name.c_str()); - if (node != NULL) +void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef node) +{ + NiNodeRef parent = node->GetParent(); + string name = node->GetName(); + vector<NiAVObjectRef> children = node->GetChildren(); + vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children); + + TSTR s1 = FormatText("Processing %s:", name.c_str()); + TSTR s2 = FormatText("Processing %s:", name.c_str()); + INode *bone = gi->GetINodeByName(name.c_str()); + if (bone != NULL) { - Matrix3 m3 = node->GetNodeTM(TimeValue(), NULL); // local translation m - master->SetBipedPos(Point3(pos.x, pos.y, pos.z), 0, node, TRUE); + if (uncontrolledDummies) + BuildControllerRefList(node, ctrlCount); - Matrix3 m(rot.rows[0].data, rot.rows[1].data, rot.rows[2].data, Point3(0,0,0)); - Matrix3 im = Inverse(m); + Matrix44 m4 = node->GetWorldTransform(); + Vector3 pos; Matrix33 rot; float scale; + m4.Decompose(pos, rot, scale); + Matrix3 m = TOMATRIX3(m4); + Point3 p = m.GetTrans(); + Quat q(m); + + s1 += FormatText(" ( %s)", PrintMatrix3(m).data()); + if (strmatch(name, master->GetRootName())) + { + // Align COM + //PosRotScaleNode(bone, p, q, 1.0f, prsPos); + PosRotScaleBiped(master, bone, p, q, 1.0f, prsPos); + } + else if (INode *pnode = bone->GetParentNode()) + { + // Reparent if necessary + if (!strmatch(parent->GetName(), pnode->GetName())) { + if (pnode = gi->GetINodeByName(parent->GetName().c_str())) { + bone->Detach(0); + pnode->AttachChild(bone); + } + } - Point3 p; Quat q; Point3 s; - DecomposeMatrix(im, p, q, s); - master->SetBipedRot(q, 0, node, TRUE); + // Hack to scale the object until it fits + for (int i=0; i<10; ++i) { + float s = CalcScale(bone, node, childNodes); + if (fabs(s-1.0f) < (FLT_EPSILON*100.0f)) + break; + s1 += FormatText(" (%g)", s); + master->SetBipedScale(TRUE, ScaleValue(Point3(s,s,s)), 0, bone); + } + PosRotScale prs = prsDefault; + PosRotScaleBiped(master, bone, p, q, scale, prs); + + // Rotation with Clavicle is useless in Figure Mode using the standard interface + // I was tring unsuccessfully to correct for it + //if (wildcmpi("Bip?? ? Clavicle", name.c_str())) { + // AngAxis a1 = CalcTransform(bone, node, childNodes); + // Matrix3 tm1 = GenerateRotMatrix(a1); + // Quat nq = TransformQuat(tm1, q); + // PosRotScaleNode(bone, p, nq, scale, prsRot); + //} + } + s2 += FormatText(" ( %s)", PrintMatrix3(bone->GetNodeTM(0)).data()); } else { - + ImportBones(node, false); } - if (Recurse) - { - vector<NiNodeRef> nodes = DynamicCast<NiNode>(block->GetChildren()); - for (vector<NiNodeRef>::iterator itr = nodes.begin(), end = nodes.end(); itr != end; ++itr){ - ScaleBiped(master, *itr, Recurse); - } + for (char *p = s1; *p != 0; ++p) if (isspace(*p)) *p = ' '; + for (char *p = s2; *p != 0; ++p) if (isspace(*p)) *p = ' '; + OutputDebugString(s1 + "\n"); + OutputDebugString(s2 + "\n"); + + for (vector<NiNodeRef>::iterator itr = childNodes.begin(), end = childNodes.end(); itr != end; ++itr){ + AlignBiped(master, *itr); } } - INode *NifImporter::CreateBone(const string& name, Point3 startPos, Point3 endPos, Point3 zAxis) { if (FPInterface * fpBones = GetCOREInterface(Interface_ID(0x438aff72, 0xef9675ac))) @@ -308,7 +468,7 @@ INode *NifImporter::CreateBone(const string& name, Point3 startPos, Point3 endPo setMAXScriptValue(o->GetReference(0), "width", 0, width); setMAXScriptValue(o->GetReference(0), "height", 0, width); } - n->ShowBone(1); + n->ShowBone(2); } return result.n; } @@ -347,41 +507,6 @@ void NifImporter::ImportBones(vector<NiNodeRef>& bones) } } -float GetObjectLength(NiAVObjectRef obj) -{ - float clen = obj->GetLocalTranslation().Magnitude(); - if (clen < (FLT_EPSILON*10)) { - if (NiTriBasedGeomRef geom = DynamicCast<NiTriBasedGeom>(obj)) { - if (NiTriBasedGeomDataRef data = geom->GetData()) { - clen = data->GetRadius() * 2.0f; - } - } - } - 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) { @@ -399,7 +524,7 @@ static bool HasUserPropBuffer(NiNodeRef node) return false; } -void NifImporter::ImportBones(NiNodeRef node) +void NifImporter::ImportBones(NiNodeRef node, bool recurse) { try { @@ -479,7 +604,7 @@ void NifImporter::ImportBones(NiNodeRef node) } } } - if (bone) + if (bone && recurse) { ImportBones(childNodes); } diff --git a/NifImport/MaxNifTools.ini b/NifImport/MaxNifTools.ini index b2fbbe2..0259437 100644 --- a/NifImport/MaxNifTools.ini +++ b/NifImport/MaxNifTools.ini @@ -72,7 +72,7 @@ EnableAnimations=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 +; Apply the overall transform to skin and bones. Default: 1 ApplyOverallTransformToSkinAndBones=1 ; [Applications] @@ -132,4 +132,4 @@ TextureRootPaths=$(ExtractFolder) TextureExtensions=.dds;.bmp;.tga TextureSearchPaths= GoToSkeletonBindPosition=1 - +ApplyOverallTransformToSkinAndBones=0 \ No newline at end of file diff --git a/NifImport/NIFImporter.h b/NifImport/NIFImporter.h index c268b44..015a62e 100644 --- a/NifImport/NIFImporter.h +++ b/NifImport/NIFImporter.h @@ -75,12 +75,9 @@ public: bool HasSkeleton(); bool IsBiped(); void ImportBones(vector<Niflib::NiNodeRef>& bones); - void ImportBones(Niflib::NiNodeRef blocks); + void ImportBones(Niflib::NiNodeRef blocks, bool recurse = true); void ImportBipeds(vector<Niflib::NiNodeRef>& blocks); void AlignBiped(IBipMaster* master, Niflib::NiNodeRef block); - void PositionBiped(IBipMaster* master, Niflib::NiNodeRef block, bool Recurse = false); - void RotateBiped(IBipMaster* master, Niflib::NiNodeRef block, bool Recurse = false); - void ScaleBiped(IBipMaster* master, Niflib::NiNodeRef block, bool Recurse = false); bool ImportMeshes(Niflib::NiNodeRef block); string FindImage(const string& name); -- GitLab