Skip to content
Snippets Groups Projects
ImportSkeleton.cpp 24.7 KiB
Newer Older
/**********************************************************************
*<
FILE: ImportSkeleton.cpp

DESCRIPTION:	Skeleton import routines

CREATED BY: tazpn (Theo)

figment's avatar
figment committed
HISTORY:

*>	Copyright (c) 2006, All Rights Reserved.
**********************************************************************/
#include "stdafx.h"
#include "MaxNifImport.h"
Tazpn's avatar
Tazpn committed
#ifdef USE_BIPED
Tazpn's avatar
Tazpn committed
#endif
#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>

using namespace Niflib;

struct NiNodeNameEquivalence : public NumericStringEquivalence
{
figment's avatar
figment committed
	bool operator()(const NiNodeRef& n1, const NiNodeRef& n2) const {
		return NumericStringEquivalence::operator()(n1->GetName(), n2->GetName());
	}
};

void GoToSkeletonBindPosition(vector<NiNodeRef>& blocks)
{
figment's avatar
figment committed
	//Send all skeleton roots to bind position
	for (unsigned int i = 0; i < blocks.size(); ++i) {
		NiNodeRef node = blocks[i];
		if (node != nullptr && node->IsSkeletonRoot()) {
			node->GoToSkeletonBindPosition();
		}
	}
figment's avatar
figment committed
	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;
figment's avatar
figment committed
static void BuildControllerRefList(NiNodeRef node, map<tstring, int>& ctrlCount)
figment's avatar
figment committed
	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 = DynamicCast<NiNode>(multiCtrl->GetExtraTargets());
			nlist.insert(nlist.end(), extra.begin(), extra.end());
		}

		for (list<NiNodeRef>::iterator nitr = nlist.begin(); nitr != nlist.end(); ++nitr) {
			tstring name = A2TString((*nitr)->GetName());
			map<tstring, int>::iterator citr = ctrlCount.find(name);
			if (citr != ctrlCount.end())
				++(*citr).second;
			else
				ctrlCount[name] = 1;
		}
	}
figment's avatar
figment committed
	if (!skeletonCheck.empty()) {
		vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, skeletonCheck.c_str());
		return !bipedRoots.empty();
	}
	return false;
figment's avatar
figment committed
	if (HasSkeleton()) {
		NiNodeRef rootNode = root;
		if (rootNode) {
			list<NiExtraDataRef> extraData = rootNode->GetExtraData();
			if (!extraData.empty()) {
				if (BSXFlagsRef flags = SelectFirstObjectOfType<BSXFlags>(extraData)) {
					return (flags->GetData() & 0x4);
				}
			}
		}
	}
	return false;
}

void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes)
{
figment's avatar
figment committed
	IBipMaster* master = nullptr;
	try
	{
		vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, "Bip??");
		std::stable_sort(bipedRoots.begin(), bipedRoots.end(), NiNodeNameEquivalence());
		for (vector<NiNodeRef>::iterator bipedItr = bipedRoots.begin(); bipedItr != bipedRoots.end(); ++bipedItr)
		{
			tstring bipname = A2TString((*bipedItr)->GetName());
			tstring match = bipname + TEXT("*");
			vector<NiNodeRef> bipedNodes = SelectNodesByName(nodes, match.c_str());

			float height = this->bipedHeight;
figment's avatar
figment committed
			list<NiExtraDataRef> extraData = nodes[0]->GetExtraData();
			if (!extraData.empty()) {
				BSBoundRef bound = SelectFirst<BSBound>(extraData);
				if (bound) {
					array<float, 6> floats = bound->GetUnknownFloats();
					height = floats[2] * 2.0f;
				}
			}
figment's avatar
figment committed
			float angle = TORAD(bipedAngle);
			Point3 wpos(0.0f, 0.0f, 0.0f);
			BOOL arms = (CountNodesByName(bipedNodes, FormatText(TEXT("%s L UpperArm"), bipname.c_str())) > 0) ? TRUE : FALSE;
			BOOL triPelvis = bipedTrianglePelvis ? TRUE : FALSE;
			int nnecklinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s Neck*"), bipname.c_str()));
			int nspinelinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s Spine*"), bipname.c_str()));
			int nleglinks = 3 + CountNodesByName(bipedNodes, FormatText(TEXT("%s L HorseLink"), bipname.c_str()));
			int ntaillinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s Tail*"), bipname.c_str()));
			int npony1links = CountNodesByName(bipedNodes, FormatText(TEXT("%s Ponytail1*"), bipname.c_str()));
			int npony2links = CountNodesByName(bipedNodes, FormatText(TEXT("%s Ponytail2*"), bipname.c_str()));
			int numfingers = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Finger?"), bipname.c_str()));
			int nfinglinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Finger0*"), bipname.c_str()));
			int numtoes = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Toe?"), bipname.c_str()));
			int ntoelinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Toe0*"), bipname.c_str()));
			BOOL prop1exists = CountNodesByName(bipedNodes, FormatText(TEXT("%s Prop1"), bipname.c_str())) ? TRUE : FALSE;
			BOOL prop2exists = CountNodesByName(bipedNodes, FormatText(TEXT("%s Prop2"), bipname.c_str())) ? TRUE : FALSE;
			BOOL prop3exists = CountNodesByName(bipedNodes, FormatText(TEXT("%s Prop3"), bipname.c_str())) ? TRUE : FALSE;
			int forearmTwistLinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Fore*Twist*"), bipname.c_str()));
			int upperarmTwistLinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Up*Twist*"), bipname.c_str()));
			int thighTwistLinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Thigh*Twist*"), bipname.c_str()));
			int calfTwistLinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Calf*Twist*"), bipname.c_str()));
			int horseTwistLinks = CountNodesByName(bipedNodes, FormatText(TEXT("%s L Horse*Twist*"), bipname.c_str()));

			NiNodeRef root = nodes[0];
			IBipMaster* master = nullptr;
			if (Max8CreateNewBiped) {
				master = Max8CreateNewBiped(height, angle, wpos, arms, triPelvis,
					nnecklinks, nspinelinks, nleglinks, ntaillinks, npony1links, npony2links,
					numfingers, nfinglinks, numtoes, ntoelinks, bipedAnkleAttach, prop1exists,
					prop2exists, prop3exists, forearmTwistLinks, upperarmTwistLinks, thighTwistLinks,
					calfTwistLinks, horseTwistLinks);
			}
			else if (Max7CreateNewBiped) {
				master = Max7CreateNewBiped(height, angle, wpos, arms, triPelvis,
					nnecklinks, nspinelinks, nleglinks, ntaillinks, npony1links, npony2links,
					numfingers, nfinglinks, numtoes, ntoelinks, bipedAnkleAttach, prop1exists,
					prop2exists, prop3exists, forearmTwistLinks);
			}

			if (master)
			{
				master->SetRootName(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 for Oblivion
				RenameNode(gi, FormatText(TEXT("%s L ForeTwist"), bipname), FormatText(TEXT("%s L ForearmTwist"), bipname));
				RenameNode(gi, FormatText(TEXT("%s R ForeTwist"), bipname), FormatText(TEXT("%s R ForearmTwist"), bipname));
				RenameNode(gi, FormatText(TEXT("%s R LUpArmTwist"), bipname), FormatText(TEXT("%s L UpperArmTwist"), bipname));
				RenameNode(gi, FormatText(TEXT("%s R LUpArmTwist"), bipname), FormatText(TEXT("%s R UpperArmTwist"), bipname));

				if (NiNodeRef nifBip = FindNodeByName(nodes, bipname))
				{
					AlignBiped(master, nifBip);
				}
			}
		}
	}
	catch (exception & e)
	{
		e = e;
	}
	catch (...)
	{
	}
	if (master)
		master->EndModes(BMODE_FIGURE, TRUE);
static float CalcLength(NiNodeRef node, vector<NiAVObjectRef>& children)
figment's avatar
figment committed
	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
	{
		len = node->GetLocalTranslation().Magnitude();
	}
	return len;
figment's avatar
figment committed
	int n = bone->NumberOfChildren();
	float len = 0.0f;
	if (n > 0)
	{
		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;
figment's avatar
figment committed
	// 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(TEXT("Bip?? ? Clavicle"), name)
		|| wildcmpi(TEXT("Bip?? ? Toe?"), name)
		|| wildcmpi(TEXT("Bip?? ? Finger?"), name)
		|| wildcmpi(TEXT("Bip?? *Twist*"), name)
		);
static float CalcScale(INode *bone, NiNodeRef node, vector<NiNodeRef>& children)
{
figment's avatar
figment committed
	int n = bone->NumberOfChildren();
	if (n > 0)
	{
		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 cm = child->GetObjectTM(0);
			len1 += Length(m.GetTrans() - cm.GetTrans());

			if (NiNodeRef child2 = FindNodeByName(children, child->GetName())) {
				Matrix3 cm2 = TOMATRIX3(child2->GetWorldTransform());
				len2 += Length(m2.GetTrans() - cm2.GetTrans());
			}
		}
		return (len2 != 0.0f && len1 != 0.0f) ? (len2 / len1) : 1.0f;
	}
	return 1.0f;
void PosRotScaleBiped(IBipMaster* master, INode *n, Point3 p, Quat& q, float s, PosRotScale prs, TimeValue t = 0)
{
figment's avatar
figment committed
	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);
figment's avatar
figment committed
	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));
figment's avatar
figment committed
	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.GetRow(0)[0] = rcos + u*u*(1 - rcos);
	m.GetRow(1)[0] = w * rsin + v*u*(1 - rcos);
	m.GetRow(2)[0] = -v * rsin + w*u*(1 - rcos);
	m.GetRow(0)[1] = -w * rsin + u*v*(1 - rcos);
	m.GetRow(1)[1] = rcos + v*v*(1 - rcos);
	m.GetRow(2)[1] = u * rsin + w*v*(1 - rcos);
	m.GetRow(0)[2] = v * rsin + u*w*(1 - rcos);
	m.GetRow(1)[2] = -u * rsin + v*w*(1 - rcos);
	m.GetRow(2)[2] = rcos + w*w*(1 - rcos);
	return m;
static AngAxis CalcTransform(INode *bone, NiNodeRef node, vector<NiNodeRef>& children)
{
figment's avatar
figment committed
	Matrix3 mr(TRUE);
	int n = bone->NumberOfChildren();
	if (n > 0)
	{
		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, 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::AlignBiped(IBipMaster* master, NiNodeRef node)
{
figment's avatar
figment committed
	NiNodeRef parent = node->GetParent();
	tstring pname = A2TString(parent->GetName());
	tstring name = A2TString(node->GetName());
	vector<NiAVObjectRef> children = node->GetChildren();
	vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);

	TSTR s1 = FormatText(TEXT("Processing %s:"), name.c_str());
	TSTR s2 = FormatText(TEXT("Processing %s:"), name.c_str());
	INode *bone = GetNode(node);
	if (bone != nullptr)
	{
		if (uncontrolledDummies)
			BuildControllerRefList(node, ctrlCount);

		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(TEXT(" ( %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(pname, pnode->GetName())) {
				if (pnode = FindNode(parent)) {
					bone->Detach(0);
					pnode->AttachChild(bone);
				}
			}

			// 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(TEXT(" (%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(TEXT("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(TEXT(" ( %s)"), PrintMatrix3(bone->GetNodeTM(0)).data());
	}
	else
	{
		ImportBones(node, false);
	}
	for (TCHAR *p = DataForWrite(s1); *p != 0; ++p) if (_istspace(*p)) *p = TEXT(' ');
	for (TCHAR *p = DataForWrite(s2); *p != 0; ++p) if (_istspace(*p)) *p = TEXT(' ');
figment's avatar
figment committed
	OutputDebugString(s1 + TEXT("\n"));
	OutputDebugString(s2 + TEXT("\n"));

	for (vector<NiNodeRef>::iterator itr = childNodes.begin(), end = childNodes.end(); itr != end; ++itr) {
		AlignBiped(master, *itr);
	}
figment's avatar
figment committed
INode *NifImporter::CreateBone(const tstring& name, Point3 startPos, Point3 endPos, Point3 zAxis)
figment's avatar
figment committed
	if (FPInterface * fpBones = GetCOREInterface(Interface_ID(0x438aff72, 0xef9675ac)))
	{
		FunctionID createBoneID = fpBones->FindFn(TEXT("createBone"));
		FPValue result;
		FPParams params(3, TYPE_POINT3, &startPos, TYPE_POINT3, &endPos, TYPE_POINT3, &zAxis);
		FPStatus status = fpBones->Invoke(createBoneID, result, &params);
		if (status == FPS_OK && result.type == TYPE_INODE)
		{
			if (INode *n = result.n)
			{
				SetNodeName(n, name.c_str());

				float len = Length(endPos - startPos);
				float width = max(minBoneWidth, min(maxBoneWidth, len * boneWidthToLengthRatio));
				if (Object* o = n->GetObjectRef())
				{
					setMAXScriptValue(o->GetReference(0), TEXT("width"), 0, width);
					setMAXScriptValue(o->GetReference(0), TEXT("height"), 0, width);
				}
#if VERSION_3DSMAX > ((6000<<16)+(15<<8)+0) // Version 6
figment's avatar
figment committed
				n->BoneAsLine(1);
				n->ShowBone(2);
figment's avatar
figment committed
				//n->BoneAsLine(1);
				n->ShowBone(1);
figment's avatar
figment committed
			}
			return result.n;
		}
		fpBones->ReleaseInterface();
	}
	return nullptr;
figment's avatar
figment committed
INode *NifImporter::CreateHelper(const tstring& name, Point3 startPos)
figment's avatar
figment committed
	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)));
figment's avatar
figment committed
		if (INode *n = CreateImportNode(name.c_str(), ob, nullptr)) {
			n->SetWireColor(RGB(192, 192, 192));
figment's avatar
figment committed
			Quat q; q.Identity();
			PosRotScaleNode(n, startPos, q, 1.0f, prsPos);

#if VERSION_3DSMAX > ((6000<<16)+(15<<8)+0) // Version 6
figment's avatar
figment committed
			n->BoneAsLine(dummyBonesAsLines ? 1 : 0);
			n->ShowBone(2);
figment's avatar
figment committed
			//n->BoneAsLine(1);
			n->ShowBone(1);
figment's avatar
figment committed
			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  nullptr;
figment's avatar
figment committed
INode *NifImporter::CreateCamera(const tstring& name)
figment's avatar
figment committed
	if (GenCamera *ob = (GenCamera *)gi->CreateInstance(CAMERA_CLASS_ID, Class_ID(SIMPLE_CAM_CLASS_ID, 0))) {
		ob->Enable(1);
		ob->NewCamera(0);
		ob->SetFOV(0, TORAD(75.0f));
		if (INode *n = gi->CreateObjectNode(ob)) {
			//if (INode *n = CreateImportNode(name.c_str(), ob, nullptr)) {
			n->Hide(TRUE);
			n->BoneAsLine(1);
			return n;
		}
	}
	return nullptr;
void NifImporter::ImportBones(vector<NiNodeRef>& bones)
{
figment's avatar
figment committed
	for (vector<NiNodeRef>::iterator itr = bones.begin(), end = bones.end(); itr != end; ++itr) {
		ImportBones(*itr);
	}
figment's avatar
figment committed
static bool HasControllerRef(map<tstring, int>& ctrlCount, const tstring& name)
figment's avatar
figment committed
	return (ctrlCount.find(name) != ctrlCount.end());
figment's avatar
figment committed
	if (node) {
		if (NiStringExtraDataRef data = SelectFirstObjectOfType<NiStringExtraData>(node->GetExtraData())) {
			if (strmatch(data->GetName(), "UserPropBuffer"))
				return true;
		}
	}
	return false;
extern Point3 TOEULER(const Matrix3 &m);
figment's avatar
figment committed
inline Point3 TORAD(const Point3& p) {
	return Point3(TORAD(p[0]), TORAD(p[1]), TORAD(p[2]));
void NifImporter::ImportBones(NiNodeRef node, bool recurse)
figment's avatar
figment committed
	try
	{
		if (uncontrolledDummies)
			BuildControllerRefList(node, ctrlCount);

		bool setnoname = false;
		tstring name = A2TString(node->GetName());
		if (name.empty())
		{
			name = FormatText(TEXT("noname:%d"), ++unnamedCounter);
			setnoname = true;
		}

		vector<NiAVObjectRef> children = node->GetChildren();
		vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);

		NiAVObject::CollisionType cType = node->GetCollisionMode();
		if (children.empty() && name == TEXT("Bounding Box"))
			return;

		// Do all node manipulations here
		NiNodeRef parent = node->GetParent();
		tstring parentname = A2TString((parent ? parent->GetName() : ""));
		Matrix44 m4 = node->GetWorldTransform();

		// Check for Prn strings and change parent if necessary
		if (supportPrnStrings) {
			list<NiStringExtraDataRef> strings = DynamicCast<NiStringExtraData>(node->GetExtraData());
			for (list<NiStringExtraDataRef>::iterator itr = strings.begin(); itr != strings.end(); ++itr) {
				if (strmatch((*itr)->GetName(), "Prn")) {
					parentname = A2TString((*itr)->GetData());
					if (INode *pn = GetNode(parentname)) {
						// Apparently Heads tend to need to be rotated 90 degrees on import for 
						if (!rotate90Degrees.empty() && wildmatch(rotate90Degrees, parentname)) {
							m4 *= TOMATRIX4(RotateYMatrix(TORAD(90)));
						}
						m4 *= TOMATRIX4(pn->GetObjTMAfterWSM(0, nullptr));
					}
				}
			}
		}

		float len = node->GetLocalTranslation().Magnitude();

		// Remove NonAccum nodes and merge into primary bone
		if (mergeNonAccum && wildmatch(TEXT("* NonAccum"), name) && parent)
		{
			tstring realname = name.substr(0, name.length() - 9);
			if (strmatch(realname, parentname))
			{
				Matrix44 tm = parent->GetLocalTransform() * node->GetLocalTransform();
				name = realname;
				len += tm.GetTranslation().Magnitude();
				parent = parent->GetParent();
			}
		}

		PosRotScale prs = prsDefault;
		Vector3 pos; Matrix33 rot; float scale;
		m4.Decompose(pos, rot, scale);

		Matrix3 im = TOMATRIX3(m4);
		Point3 p = im.GetTrans();
		Quat q(im);
		//q.Normalize();
		Vector3 ppos;
		Point3 zAxis(0, 0, 1);
		bool hasChildren = !children.empty();
		if (hasChildren) {
			float len = 0.0f;
			for (vector<NiAVObjectRef>::iterator itr = children.begin(), end = children.end(); itr != end; ++itr) {
				len += GetObjectLength(*itr);
			}
			len /= float(children.size());
			ppos = pos + Vector3(len, 0.0f, 0.0f); // just really need magnitude as rotation will take care of positioning
		}
		else if (parent)
		{
			ppos = pos + Vector3(len / 3.0f, 0.0f, 0.0f);
		}
		Point3 pp(ppos.x, ppos.y, ppos.z);

		Point3 qp = TORAD(TOEULER(im));


		INode *bone = nullptr;
		if (!doNotReuseExistingBones) // Games like BC3 reuse the same bone names
		{
			bone = FindNode(node);
			if (bone == nullptr)
				bone = GetNode(name);
		}
		if (bone)
		{
			// Is there a better way of TEXT("Affect Pivot Only") behaviors?
			INode *pinode = bone->GetParentNode();
			if (pinode)
				bone->Detach(0, 1);
			PosRotScaleNode(bone, p, q, scale, prs);
			if (pinode)
				pinode->AttachChild(bone, 1);
		}
		else
		{
			bool isDummy = importBonesAsDummy
				|| ((uncontrolledDummies && !HasControllerRef(ctrlCount, name))
					|| (!dummyNodeMatches.empty() && wildmatch(dummyNodeMatches, name))
					|| (convertBillboardsToDummyNodes && node->IsDerivedType(NiBillboardNode::TYPE))
					);
			if (wildmatch(TEXT("Camera*"), name)) {
				if (enableCameras) {
					if (bone = CreateCamera(name)) {
						PosRotScaleNode(bone, p, q, scale, prs);
						bone->Hide(node->GetVisibility() ? FALSE : TRUE);
					}
				}
			}
			else if (isDummy && createNubsForBones) {
				bone = CreateHelper(name, p);
				PosRotScaleNode(bone, p, q, scale, prs);
				//bone->Hide(node->GetVisibility() ? FALSE : TRUE);
			}
			else if (bone = CreateBone(name, p, pp, zAxis)) {
				PosRotScaleNode(bone, p, q, scale, prs);
				bone->Hide(node->GetVisibility() ? FALSE : TRUE);
			}
			if (bone)
			{
				if (!parentname.empty())
				{
					if (mergeNonAccum && wildmatch(TEXT("* NonAccum"), parentname)) {
						parentname = parentname.substr(0, parentname.length() - 9);
					}
					if (INode *pn = GetNode(parentname))
						pn->AttachChild(bone, 1);
				}
				if (setnoname)
					bone->SetUserPropBool(NP_NONAME, TRUE);
				else
					RegisterNode(node, bone);
			}
		}
		// Import UPB
		if (bone) ImportUPB(bone, node);

		// Import Havok Collision Data surrounding node,  
		//   unfortunately this causes double import of collision so I'm disabling it for now.
		if (enableCollision && node->GetParent()) {
			ImportCollision(node);
		}

		if (bone && recurse)
		{
			ImportBones(childNodes);
		}
	}
	catch (exception & e)
	{
		e = e;
	}
	catch (...)
	{
	}
}

bool NifImporter::ImportUPB(INode *node, Niflib::NiNodeRef block)
{
figment's avatar
figment committed
	USES_CONVERSION;
	TCHAR tbuf[128];

	if (!importUPB)
		return false;

	bool ok = false;
	if (node && block)
	{
		list<NiStringExtraDataRef> strings = DynamicCast<NiStringExtraData>(block->GetExtraData());
		for (list<NiStringExtraDataRef>::iterator itr = strings.begin(); itr != strings.end(); ++itr) {
			if (strmatch((*itr)->GetName(), "UserPropBuffer") || strmatch((*itr)->GetName(), "UPB")) {
				char buffer[1048];
				istringstream istr((*itr)->GetData(), ios_base::out);
				while (!istr.eof()) {
					char *line = buffer;
					buffer[0] = 0;
					istr.getline(buffer, _countof(buffer) - 1);
					if (LPSTR equals = strchr(line, '=')) {
						*equals++ = 0;
						Trim(line), Trim(equals);
						if (line[0] && equals[0]) {
							TSTR tline = A2THelper(tbuf, line, _countof(tbuf));
							TSTR tequals = A2THelper(tbuf, equals, _countof(tbuf));
							node->SetUserPropString(tline, tequals);
							ok |= true;
						}
					}
					else {
						Trim(line);
						int len = strlen(line);
						// Handle bethesda special values?
						if (len > 0 && line[len - 1] == TEXT('#')) {
							TSTR buf, value;
							node->GetUserPropBuffer(buf);
							value.append(A2THelper(tbuf, line, _countof(tbuf)));
							value.append(TEXT("\r\n")).append(buf);
							if (wildmatch(TEXT("BSBoneLOD#*"), value)) {
								TCHAR *data = DataForWrite(value);
figment's avatar
figment committed
								data[0] = TEXT('N');
								data[1] = TEXT('i'); // Use NIBoneLOD to be compatible with Civ4 code
							}
							node->SetUserPropBuffer(value);
						}
					}
				}
			}
		}
	}
	return ok;