Newer
Older
/**********************************************************************
*<
FILE: ImportSkeleton.cpp
DESCRIPTION: Skeleton import routines
CREATED BY: tazpn (Theo)
HISTORY:
*> Copyright (c) 2006, All Rights Reserved.
**********************************************************************/
#include "stdafx.h"
#include "MaxNifImport.h"
#include <cs/BipedApi.h>
#include <obj/NiTriBasedGeom.h>
#include <obj/NiTriBasedGeomData.h>
#include <obj/NiTimeController.h>
Tazpn
committed
#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
{
bool operator()(const NiNodeRef& n1, const NiNodeRef& n2) const {
return NumericStringEquivalence::operator()(n1->GetName(), n2->GetName());
}
};
void GoToSkeletonBindPosition(vector<NiNodeRef>& blocks)
{
//Send all skeleton roots to bind position
for (uint i = 0; i < blocks.size(); ++i) {
NiNodeRef node = blocks[i];
if ( node != NULL && node->IsSkeletonRoot() ) {
node->GoToSkeletonBindPosition();
}
}
}
Tazpn
committed
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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()){
vector<NiNodeRef> bipedRoots = SelectNodesByName(nodes, skeletonCheck.c_str());
return !bipedRoots.empty();
}
return false;
}
bool NifImporter::IsBiped()
{
if (hasSkeleton){
Tazpn
committed
NiNodeRef rootNode = root;
if (rootNode){
list<NiExtraDataRef> extraData = rootNode->GetExtraData();
if (!extraData.empty()) {
return ( SelectFirstObjectOfType<BSBound>(extraData) != NULL );
}
}
}
return false;
}
void NifImporter::ImportBipeds(vector<NiNodeRef>& nodes)
{
#ifdef USE_BIPED
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
IBipMaster* master = NULL;
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)
{
string bipname = (*bipedItr)->GetName();
string match = bipname + "*";
vector<NiNodeRef> bipedNodes = SelectNodesByName(nodes, match.c_str());
float height = this->bipedHeight;
#if USE_CUSTOM_BSBOUND
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;
}
}
#endif
float angle = TORAD(bipedAngle);
Point3 wpos(0.0f,0.0f,0.0f);
BOOL arms = (CountNodesByName(bipedNodes, FormatText("%s L UpperArm", bipname.c_str())) > 0) ? TRUE : FALSE;
BOOL triPelvis = bipedTrianglePelvis ? TRUE : FALSE;
int nnecklinks=CountNodesByName(bipedNodes, FormatText("%s Neck*", bipname.c_str()));
int nspinelinks=CountNodesByName(bipedNodes, FormatText("%s Spine*", bipname.c_str()));
int nleglinks = 3 + CountNodesByName(bipedNodes, FormatText("%s L HorseLink", bipname.c_str()));
int ntaillinks = CountNodesByName(bipedNodes, FormatText("%s Tail*", bipname.c_str()));
int npony1links = CountNodesByName(bipedNodes, FormatText("%s Ponytail1*", bipname.c_str()));
int npony2links = CountNodesByName(bipedNodes, FormatText("%s Ponytail2*", bipname.c_str()));
int numfingers = CountNodesByName(bipedNodes, FormatText("%s L Finger?", bipname.c_str()));
int nfinglinks = CountNodesByName(bipedNodes, FormatText("%s L Finger0*", bipname.c_str()));
int numtoes = CountNodesByName(bipedNodes, FormatText("%s L Toe?", bipname.c_str()));
int ntoelinks = CountNodesByName(bipedNodes, FormatText("%s L Toe0*", bipname.c_str()));
BOOL prop1exists = CountNodesByName(bipedNodes, FormatText("%s Prop1", bipname.c_str())) ? TRUE : FALSE;
BOOL prop2exists = CountNodesByName(bipedNodes, FormatText("%s Prop2", bipname.c_str())) ? TRUE : FALSE;
BOOL prop3exists = CountNodesByName(bipedNodes, FormatText("%s Prop3", bipname.c_str())) ? TRUE : FALSE;
int forearmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Fore*Twist*", bipname.c_str()));
int upperarmTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Up*Twist*", bipname.c_str()));
int thighTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Thigh*Twist*", bipname.c_str()));
int calfTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Calf*Twist*", bipname.c_str()));
int horseTwistLinks = CountNodesByName(bipedNodes, FormatText("%s L Horse*Twist*", bipname.c_str()));
NiNodeRef root = nodes[0];
IBipMaster* master = NULL;
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)
{
Tazpn
committed
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();
Tazpn
committed
// 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));
Tazpn
committed
if (NiNodeRef nifBip = FindNodeByName(nodes, bipname))
{
AlignBiped(master, nifBip);
}
}
}
}
catch( exception & e )
{
e=e;
}
catch( ... )
{
}
if (master)
master->EndModes(BMODE_FIGURE, TRUE);
#endif
}
Tazpn
committed
static float CalcLength(NiNodeRef node, vector<NiAVObjectRef>& children)
Tazpn
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
{
Tazpn
committed
len = node->GetLocalTranslation().Magnitude();
Tazpn
committed
return len;
}
Tazpn
committed
Matrix3 GetLocalTM(INode *node)
Tazpn
committed
if (INode *parent = node->GetParentNode())
Tazpn
committed
Matrix3 parentTM, nodeTM;
nodeTM = node->GetNodeTM(0);
parent = node->GetParentNode();
parentTM = parent->GetNodeTM(0);
return nodeTM*Inverse(parentTM);
}
else
{
Tazpn
committed
return node->GetNodeTM(0);
Tazpn
committed
}
static float CalcLength(INode *bone)
{
int n = bone->NumberOfChildren();
float len = 0.0f;
if (n > 0)
Tazpn
committed
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;
Tazpn
committed
len /= float(n);
Tazpn
committed
else
{
len = Length(GetLocalTM(bone).GetTrans());
}
return len;
}
Tazpn
committed
static bool HasBipedPosDOF(LPCTSTR name)
Tazpn
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("Bip?? ? Clavicle", name)
|| wildcmpi("Bip?? ? Toe?", name)
|| wildcmpi("Bip?? ? Finger?", name)
|| wildcmpi("Bip?? *Twist*", name)
);
}
Tazpn
committed
static float CalcScale(INode *bone, NiNodeRef node, vector<NiNodeRef>& children)
{
int n = bone->NumberOfChildren();
if (n > 0)
Tazpn
committed
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;
Tazpn
committed
Matrix3 cm = child->GetObjectTM(0);
len1 += Length(m.GetTrans()-cm.GetTrans());
Tazpn
committed
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;
Tazpn
committed
return 1.0f;
}
#ifdef USE_BIPED
Tazpn
committed
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);
}
Tazpn
committed
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 ) );
}
Tazpn
committed
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.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);
Tazpn
committed
return m;
}
Tazpn
committed
static AngAxis CalcTransform(INode *bone, NiNodeRef node, vector<NiNodeRef>& children)
{
Matrix3 mr(TRUE);
int n = bone->NumberOfChildren();
if (n > 0)
Tazpn
committed
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;
Tazpn
committed
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 ) );
Tazpn
committed
return mr;
#endif
Tazpn
committed
void NifImporter::AlignBiped(IBipMaster* master, NiNodeRef node)
{
#ifdef USE_BIPED
Tazpn
committed
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)
Tazpn
committed
if (uncontrolledDummies)
BuildControllerRefList(node, ctrlCount);
Tazpn
committed
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);
}
}
Tazpn
committed
// 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
{
Tazpn
committed
ImportBones(node, false);
Tazpn
committed
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);
#endif
}
INode *NifImporter::CreateBone(const string& name, Point3 startPos, Point3 endPos, Point3 zAxis)
{
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, ¶ms);
if (status == FPS_OK && result.type == TYPE_INODE)
{
if (INode *n = result.n)
{
n->SetName(const_cast<TCHAR*>(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), "width", 0, width);
setMAXScriptValue(o->GetReference(0), "height", 0, width);
}
Tazpn
committed
n->ShowBone(2);
}
return result.n;
}
fpBones->ReleaseInterface();
}
return NULL;
}
INode *NifImporter::CreateHelper(const string& name, Point3 startPos)
{
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)));
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;
}
}
Tazpn
committed
//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;
}
void NifImporter::ImportBones(vector<NiNodeRef>& bones)
{
for (vector<NiNodeRef>::iterator itr = bones.begin(), end = bones.end(); itr != end; ++itr){
ImportBones(*itr);
}
}
Tazpn
committed
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;
}
Tazpn
committed
void NifImporter::ImportBones(NiNodeRef node, bool recurse)
{
try
{
Tazpn
committed
if (uncontrolledDummies)
BuildControllerRefList(node, ctrlCount);
string name = node->GetName();
vector<NiAVObjectRef> children = node->GetChildren();
vector<NiNodeRef> childNodes = DynamicCast<NiNode>(children);
NiAVObject::CollisionType cType = node->GetCollision();
if (cType == NiAVObject::ctBoundingBox && children.empty() && name=="Bounding Box")
return;
NiNodeRef parent = node->GetParent();
PosRotScale prs = prsDefault;
Matrix44 m4 = node->GetWorldTransform();
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,0);
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
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)
{
float len = node->GetLocalTranslation().Magnitude();
ppos = pos + Vector3(len/3.0f, 0.0f, 0.0f);
}
Point3 pp(ppos.x, ppos.y, ppos.z);
INode *bone = gi->GetINodeByName(name.c_str());
if (bone)
{
// Is there a better way of "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
{
Tazpn
committed
bool isDummy = ( (uncontrolledDummies && !HasControllerRef(ctrlCount, name))
|| (!dummyNodeMatches.empty() && wildmatch(dummyNodeMatches, name))
|| (convertBillboardsToDummyNodes && node->IsDerivedType(NiBillboardNode::TypeConst()))
);
if (isDummy && createNubsForBones)
bone = CreateHelper(name, p);
else if (bone = CreateBone(name, p, pp, zAxis))
{
PosRotScaleNode(bone, p, q, scale, prs);
Tazpn
committed
if (isDummy)
bone->Hide(TRUE);
else
bone->Hide(node->GetHidden() ? TRUE : FALSE);
}
if (bone)
{
if (parent)
{
if (INode *pn = gi->GetINodeByName(parent->GetName().c_str()))
pn->AttachChild(bone, 1);
}
}
}
Tazpn
committed
if (bone && recurse)