1726 lines
43 KiB
C++
1726 lines
43 KiB
C++
// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
|
|
// Copyright (C) 2010 Winch Gate Property Limited
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as
|
|
// published by the Free Software Foundation, either version 3 of the
|
|
// License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
#include "stdafx.h"
|
|
#include "export_nel.h"
|
|
#include "export_appdata.h"
|
|
#include "nel/3d/skeleton_shape.h"
|
|
|
|
using namespace NLMISC;
|
|
using namespace NL3D;
|
|
|
|
// ***************************************************************************
|
|
|
|
#define SKIN_INTERFACE 0x00010000
|
|
|
|
// ***************************************************************************
|
|
|
|
#define SKIN_CLASS_ID Class_ID(9815843,87654)
|
|
#define PHYSIQUE_CLASS_ID Class_ID(PHYSIQUE_CLASS_ID_A, PHYSIQUE_CLASS_ID_B)
|
|
|
|
// ***************************************************************************
|
|
|
|
class ISkinContextData
|
|
{
|
|
public:
|
|
virtual int GetNumPoints()=0;
|
|
virtual int GetNumAssignedBones(int vertexIdx)=0;
|
|
virtual int GetAssignedBone(int vertexIdx, int boneIdx)=0;
|
|
virtual float GetBoneWeight(int vertexIdx, int boneIdx)=0;
|
|
|
|
virtual int GetSubCurveIndex(int vertexIdx, int boneIdx)=0;
|
|
virtual int GetSubSegmentIndex(int vertexIdx, int boneIdx)=0;
|
|
virtual float GetSubSegmentDistance(int vertexIdx, int boneIdx)=0;
|
|
virtual Point3 GetTangent(int vertexIdx, int boneIdx)=0;
|
|
virtual Point3 GetOPoint(int vertexIdx, int boneIdx)=0;
|
|
|
|
virtual void SetWeight(int vertexIdx, int boneIdx, float weight)=0;
|
|
virtual void SetWeight(int vertexIdx, INode* bone, float weight)=0;
|
|
virtual void SetWeights(int vertexIdx, Tab<int> boneIdx, Tab<float> weights)=0;
|
|
virtual void SetWeights(int vertexIdx, INodeTab boneIdx, Tab<float> weights)=0;
|
|
|
|
};
|
|
|
|
// ***************************************************************************
|
|
|
|
class ISkin
|
|
{
|
|
public:
|
|
ISkin() {}
|
|
~ISkin() {}
|
|
virtual int GetBoneInitTM(INode *pNode, Matrix3 &InitTM, bool bObjOffset = false)=0;
|
|
virtual int GetSkinInitTM(INode *pNode, Matrix3 &InitTM, bool bObjOffset = false)=0;
|
|
virtual int GetNumBones()=0;
|
|
virtual INode *GetBone(int idx)=0;
|
|
virtual DWORD GetBoneProperty(int idx)=0;
|
|
virtual ISkinContextData *GetContextInterface(INode *pNode)=0;
|
|
|
|
virtual BOOL AddBone(INode *bone)=0;
|
|
virtual BOOL AddBones(INodeTab *bones)=0;
|
|
virtual BOOL RemoveBone(INode *bone)=0;
|
|
virtual void Invalidate()=0;
|
|
};
|
|
|
|
// ***************************************************************************
|
|
|
|
void CExportNel::buildSkeletonShape (CSkeletonShape& skeletonShape, INode& node, mapBoneBindPos* mapBindPos, TInodePtrInt& mapId,
|
|
TimeValue time)
|
|
{
|
|
// Build a bone vector
|
|
std::vector<CBoneBase> bonesArray;
|
|
|
|
// Counter
|
|
sint32 idCount=0;
|
|
|
|
// Set for node name
|
|
std::set<std::string> nameSet;
|
|
|
|
// Parse the tree
|
|
buildSkeleton (bonesArray, node, mapBindPos, mapId, nameSet, time, idCount);
|
|
|
|
// Then build the object
|
|
skeletonShape.build (bonesArray);
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CExportNel::isBipedNode(INode &node)
|
|
{
|
|
bool ret= false;
|
|
Control *c= node.GetTMController();
|
|
if( c && (
|
|
c->ClassID() == BIPSLAVE_CONTROL_CLASS_ID ||
|
|
c->ClassID() == BIPBODY_CONTROL_CLASS_ID ))
|
|
ret= true;
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
bool CExportNel::getNELUnHeritFatherScale(INode &node)
|
|
{
|
|
// Default.
|
|
bool ret= false;
|
|
|
|
|
|
/* If my parent exist, and if my parent is a BipedNode, always suppose I must unherit scale.
|
|
This is because Biped NodeTM always have a Scale 1,1,1. Hence Sons bones do not herit scale.
|
|
*/
|
|
INode *parentNode= node.GetParentNode();
|
|
if(parentNode && isBipedNode(*parentNode) )
|
|
{
|
|
ret= true;
|
|
}
|
|
// Else, test the std way.
|
|
else
|
|
{
|
|
// Get the TM controler
|
|
Control *c;
|
|
c = node.GetTMController();
|
|
|
|
// If a TM controler exist
|
|
if (c)
|
|
{
|
|
// Get the inherit flags
|
|
DWORD flags=c->GetInheritanceFlags();
|
|
|
|
// Unherit scale if all scale inherit flags are cleared
|
|
ret= (flags&(INHERIT_SCL_X|INHERIT_SCL_Y|INHERIT_SCL_Z))!=0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
INode *CExportNel::getNELScaleReferenceNode(INode &node)
|
|
{
|
|
INode *referenceNode= NULL;
|
|
|
|
bool exportScale= getScriptAppData (&node, NEL3D_APPDATA_EXPORT_BONE_SCALE, BST_UNCHECKED)==BST_CHECKED;
|
|
// Get the reference node
|
|
if(exportScale)
|
|
{
|
|
std::string boneScaleNameExt= getScriptAppData (&node, NEL3D_APPDATA_EXPORT_BONE_SCALE_NAME_EXT, "");
|
|
if(!boneScaleNameExt.empty())
|
|
{
|
|
std::string boneScaleName= getName(node) + boneScaleNameExt;
|
|
// Get the reference node
|
|
referenceNode= _Ip->GetINodeByName(boneScaleName.c_str());
|
|
}
|
|
}
|
|
|
|
return referenceNode;
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CExportNel::getNELBoneLocalScale(INode &node, TimeValue time, NLMISC::CVector &nelScale)
|
|
{
|
|
// To get the correct local Scale, we use an other bone as reference (if present)
|
|
INode *referenceNode= getNELScaleReferenceNode(node);
|
|
|
|
// get the Max local pos.
|
|
Matrix3 localTM (TRUE);
|
|
getLocalMatrix (localTM, node, time);
|
|
NLMISC::CVector maxScale;
|
|
NLMISC::CQuat maxRot;
|
|
NLMISC::CVector maxPos;
|
|
decompMatrix (maxScale, maxRot, maxPos, localTM);
|
|
|
|
|
|
// Biped node case, take the scale from ratio with the reference node.
|
|
if(isBipedNode(node))
|
|
{
|
|
// Replace scale with ratio from reference value, if possible
|
|
if (referenceNode)
|
|
{
|
|
ScaleValue scaleValue;
|
|
// get my Offset scale.
|
|
CVector myScale;
|
|
scaleValue = node.GetObjOffsetScale();
|
|
myScale.x= scaleValue.s.x;
|
|
myScale.y= scaleValue.s.y;
|
|
myScale.z= scaleValue.s.z;
|
|
|
|
// get its Offset scale.
|
|
CVector refScale;
|
|
scaleValue = referenceNode->GetObjOffsetScale();
|
|
refScale.x= scaleValue.s.x;
|
|
refScale.y= scaleValue.s.y;
|
|
refScale.z= scaleValue.s.z;
|
|
|
|
// Get The ratio as the result
|
|
nelScale.x= myScale.x / refScale.x;
|
|
nelScale.y= myScale.y / refScale.y;
|
|
nelScale.z= myScale.z / refScale.z;
|
|
}
|
|
else
|
|
{
|
|
// Not present, suppose no scale.
|
|
nelScale.set(1,1,1);
|
|
}
|
|
|
|
}
|
|
// get the scale from std way.
|
|
else
|
|
{
|
|
// We are a normal node here, not biped.
|
|
|
|
/* If this node do not inherit scale (ie must unherit), then we must not take the localScale computed
|
|
with getLocalMatrix. In this case, the local Scale is simply the NodeTM scale.
|
|
*/
|
|
if( getNELUnHeritFatherScale(node) )
|
|
{
|
|
Matrix3 nodeTM;
|
|
nodeTM = node.GetNodeTM (time);
|
|
decompMatrix (maxScale, maxRot, maxPos, nodeTM);
|
|
// Get the scale from worldMatrix.
|
|
nelScale= maxScale;
|
|
}
|
|
else
|
|
{
|
|
// Get the scale from localMatrix.
|
|
nelScale= maxScale;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
void CExportNel::getNELBoneLocalTM(INode &node, TimeValue time,
|
|
NLMISC::CVector &nelScale, NLMISC::CQuat &nelQuat, NLMISC::CVector &nelPos)
|
|
{
|
|
// get the Max local pos.
|
|
Matrix3 localTM (TRUE);
|
|
getLocalMatrix (localTM, node, time);
|
|
NLMISC::CVector maxScale;
|
|
NLMISC::CQuat maxRot;
|
|
NLMISC::CVector maxPos;
|
|
decompMatrix (maxScale, maxRot, maxPos, localTM);
|
|
|
|
// get bone Rot.
|
|
//=============
|
|
|
|
// Same Rot.
|
|
nelQuat= maxRot;
|
|
nelQuat.normalize();
|
|
|
|
|
|
// get Bone Scale.
|
|
//=============
|
|
|
|
// Get the NEL local scale.
|
|
getNELBoneLocalScale(node, time, nelScale);
|
|
|
|
// get bone Pos.
|
|
//=============
|
|
|
|
// Default is same pos.
|
|
nelPos= maxPos;
|
|
|
|
/* The following is important To have correct NEL position
|
|
Let me explain:
|
|
- Biped bones NodeTM have correct World Position, but NodeTM.Scale == 1,1,1 (always)
|
|
We can get the bone local scale from OffsetScale, this why we need a Reference Node, to perform the
|
|
difference (because the Reference Node ofsset scale is not a 1,1,1 scale)
|
|
- Nel bones Pos are the LOCAL pos, and they INHERIT parent scale (like in Maya).
|
|
Nel UnheritScale is used to unherit the scale from father to the rotation/scale only part of the son.
|
|
The problem is that if we export Position default pos from the current scaled "node", we will have the
|
|
"already scaled from father Local Pos" from Biped. Instead we want the "not scaled from father Local Pos"
|
|
because NEL will do the "scaled from father" job in RealTime.
|
|
|
|
So to get the correct local Position, we must remove the fahter Nel Local Scale.
|
|
*/
|
|
|
|
// If my father is a biped bone, then do special stuff.
|
|
INode *parentNode= node.GetParentNode();
|
|
// NB: don't need to test If I must unherit scale, because sons of Biped nodes always unherit scale...
|
|
if(parentNode && isBipedNode(*parentNode))
|
|
{
|
|
/* In this case, I must remove my father Nel scale, because the biped NodeTM has a scale 1,1,1.
|
|
Hence the Local position, computed with getLocalMatrix(), we have is false.
|
|
*/
|
|
CVector fatherScale;
|
|
// NB: avoid calling getNELBoneLocalTM() for father else Recursive call untill to the root !!!
|
|
// We need just the scale here.
|
|
getNELBoneLocalScale(*parentNode, time, fatherScale);
|
|
nelPos.x/= fatherScale.x;
|
|
nelPos.y/= fatherScale.y;
|
|
nelPos.z/= fatherScale.z;
|
|
}
|
|
}
|
|
|
|
|
|
// ***************************************************************************
|
|
|
|
void CExportNel::buildSkeleton (std::vector<CBoneBase>& bonesArray, INode& node, mapBoneBindPos* mapBindPos, TInodePtrInt& mapId,
|
|
std::set<std::string> &nameSet, TimeValue time, sint32& idCount, sint32 father)
|
|
{
|
|
// **** Save the current the id
|
|
int id=idCount;
|
|
|
|
// **** Create a bone
|
|
CBoneBase bone;
|
|
|
|
// ** Set Name and id
|
|
|
|
// Bone name
|
|
bone.Name=getName (node);
|
|
|
|
// Inserted ?
|
|
if (!nameSet.insert (bone.Name).second)
|
|
bone.Name+="_Second";
|
|
|
|
// Id of the father
|
|
bone.FatherId=father;
|
|
|
|
// Insert id with name
|
|
mapId.insert (TInodePtrInt::value_type(&node, id));
|
|
|
|
// ** Set inherit flags
|
|
|
|
// Must remove Scale from my father??
|
|
bone.UnheritScale= getNELUnHeritFatherScale(node);
|
|
|
|
|
|
// **** Set default tracks
|
|
|
|
// Get the Nel Local TM
|
|
NLMISC::CVector nelScale;
|
|
NLMISC::CQuat nelRot;
|
|
NLMISC::CVector nelPos;
|
|
getNELBoneLocalTM(node, time, nelScale, nelRot, nelPos);
|
|
|
|
// Root must be exported with Identity because path are setuped interactively in the root of the skeleton
|
|
if(id==0)
|
|
{
|
|
// Keep only the scale, because this last is not animated.
|
|
nelPos= CVector::Null;
|
|
nelRot= CQuat::Identity;
|
|
}
|
|
|
|
// Set the default tracks
|
|
bone.DefaultScale=nelScale;
|
|
bone.DefaultRotQuat=nelRot;
|
|
bone.DefaultRotEuler=CVector (0,0,0);
|
|
bone.DefaultPos=nelPos;
|
|
|
|
|
|
// **** Get node bindpos matrix
|
|
Matrix3 worldTM;
|
|
bool matrixComputed=false;
|
|
if (mapBindPos)
|
|
{
|
|
// Look for an entry in the map
|
|
mapBoneBindPos::iterator bindPos=mapBindPos->find (&node);
|
|
|
|
// Found ?
|
|
if (bindPos!=mapBindPos->end())
|
|
{
|
|
// Get bind pos
|
|
worldTM=bindPos->second;
|
|
|
|
// Computed
|
|
matrixComputed=true;
|
|
}
|
|
}
|
|
|
|
// Compute the matrix the normal way ?
|
|
if (!matrixComputed)
|
|
{
|
|
// if we have a reference node, ie the one with the good original pos/rot/scale used for bindPos, must use it.
|
|
INode *referenceNode= getNELScaleReferenceNode(node);
|
|
if (referenceNode)
|
|
{
|
|
worldTM= referenceNode->GetNodeTM (time);
|
|
}
|
|
// else use mine.
|
|
else
|
|
{
|
|
worldTM= node.GetNodeTM (time);
|
|
}
|
|
}
|
|
|
|
// Set the bind pos of the bone, it is invNodeTM;
|
|
convertMatrix (bone.InvBindPos, worldTM);
|
|
bone.InvBindPos.invert();
|
|
|
|
// **** Get bone Lod disactivation
|
|
bone.LodDisableDistance= getScriptAppData (&node, NEL3D_APPDATA_BONE_LOD_DISTANCE, 0.f);
|
|
bone.LodDisableDistance= std::max(0.f, bone.LodDisableDistance);
|
|
|
|
// **** Add the bone
|
|
bonesArray.push_back (bone);
|
|
|
|
// **** Call on child
|
|
for (int children=0; children<node.NumberOfChildren(); children++)
|
|
buildSkeleton (bonesArray, *node.GetChildNode(children), mapBindPos, mapId, nameSet, time, ++idCount, id);
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
bool CExportNel::isSkin (INode& node)
|
|
{
|
|
// Return success
|
|
bool ok=false;
|
|
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, SKIN_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Modifier enabled ?
|
|
//if (skin->IsEnabled())
|
|
{
|
|
// Get a com_skin2 interface
|
|
ISkin *comSkinInterface=(ISkin*)skin->GetInterface (SKIN_INTERFACE);
|
|
|
|
// Found com_skin2 ?
|
|
if (comSkinInterface)
|
|
{
|
|
// Get local data
|
|
ISkinContextData *localData=comSkinInterface->GetContextInterface(&node);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Skinned
|
|
ok=true;
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (SKIN_INTERFACE, comSkinInterface);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Try physic
|
|
if (!ok)
|
|
{
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, PHYSIQUE_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Modifier enabled ?
|
|
//if (skin->IsEnabled())
|
|
{
|
|
// Get a com_skin2 interface
|
|
IPhysiqueExport *physiqueInterface=(IPhysiqueExport *)skin->GetInterface (I_PHYINTERFACE);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Skinned
|
|
ok=true;
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (I_PHYINTERFACE, physiqueInterface);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ok;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
uint CExportNel::buildSkinning (CMesh::CMeshBuild& buildMesh, const TInodePtrInt& skeletonShape, INode& node)
|
|
{
|
|
// Return success
|
|
uint ok=NoError;
|
|
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, SKIN_CLASS_ID);
|
|
|
|
// Build a the name array
|
|
buildMesh.BonesNames.resize (skeletonShape.size());
|
|
TInodePtrInt::const_iterator ite=skeletonShape.begin();
|
|
while (ite != skeletonShape.end())
|
|
{
|
|
// Check the id
|
|
nlassert ((uint)ite->second<buildMesh.BonesNames.size());
|
|
|
|
// Names
|
|
buildMesh.BonesNames[ite->second] = ite->first->GetName();
|
|
|
|
// Next
|
|
ite++;
|
|
}
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// ********** COMSKIN EXPORT **********
|
|
|
|
// Get a com_skin2 interface
|
|
ISkin *comSkinInterface=(ISkin*)skin->GetInterface (SKIN_INTERFACE);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (comSkinInterface);
|
|
|
|
// Found com_skin2 ?
|
|
if (comSkinInterface)
|
|
{
|
|
// Get local data
|
|
ISkinContextData *localData=comSkinInterface->GetContextInterface(&node);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (localData);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Check same vertices count
|
|
uint vertCount=localData->GetNumPoints();
|
|
|
|
// Ctrl we have the same number of vertices in the mesh and in the modifier.
|
|
if (buildMesh.Vertices.size()!=vertCount)
|
|
{
|
|
ok=InvalidSkeleton;
|
|
}
|
|
else
|
|
{
|
|
// If not the same count, return false (perhaps, the modifier is not in the good place in the stack..)
|
|
if (buildMesh.Vertices.size()==vertCount)
|
|
{
|
|
// Rebuild the array
|
|
buildMesh.SkinWeights.resize (vertCount);
|
|
|
|
// For each vertex
|
|
for (uint vert=0; vert<vertCount; vert++)
|
|
{
|
|
// Get bones count for this vertex
|
|
uint boneCount=localData->GetNumAssignedBones (vert);
|
|
|
|
// No bones, can't export
|
|
if (boneCount==0)
|
|
{
|
|
// Error
|
|
ok=VertexWithoutWeight;
|
|
break;
|
|
}
|
|
|
|
// A map of float / string
|
|
std::multimap<float, uint> weightMap;
|
|
|
|
// For each bones
|
|
for (uint bone=0; bone<boneCount; bone++)
|
|
{
|
|
// Get the bone weight
|
|
float weight=localData->GetBoneWeight (vert, bone);
|
|
|
|
// Get bone number
|
|
uint boneId=localData->GetAssignedBone (vert, bone);
|
|
|
|
// Insert in the map
|
|
weightMap.insert (std::map<float, uint>::value_type (weight, boneId));
|
|
}
|
|
|
|
// Keep only the NL3D_MESH_SKINNING_MAX_MATRIX highest bones
|
|
while (weightMap.size()>NL3D_MESH_SKINNING_MAX_MATRIX)
|
|
{
|
|
// Remove the lowest weights
|
|
weightMap.erase (weightMap.begin());
|
|
}
|
|
|
|
// Sum the NL3D_MESH_SKINNING_MAX_MATRIX highest bones
|
|
float sum=0.f;
|
|
std::multimap<float, uint>::iterator ite=weightMap.begin();
|
|
while (ite!=weightMap.end())
|
|
{
|
|
// Add to the sum
|
|
sum+=ite->first;
|
|
|
|
// Next value
|
|
ite++;
|
|
}
|
|
|
|
// Erase bones
|
|
for (uint i=0; i<NL3D_MESH_SKINNING_MAX_MATRIX; i++)
|
|
{
|
|
// Erase
|
|
buildMesh.SkinWeights[vert].MatrixId[i]=0;
|
|
buildMesh.SkinWeights[vert].Weights[i]=0;
|
|
}
|
|
|
|
// For each bones in the list, build the skin information
|
|
uint id=0;
|
|
ite=weightMap.end();
|
|
while (ite!=weightMap.begin())
|
|
{
|
|
// Previous value
|
|
ite--;
|
|
|
|
// Get the bones ID
|
|
sint32 matrixId=-1;
|
|
TInodePtrInt::const_iterator itId=skeletonShape.find (comSkinInterface->GetBone(ite->second));
|
|
if (itId!=skeletonShape.end())
|
|
matrixId=itId->second;
|
|
|
|
// Find the bone ?
|
|
if (matrixId==-1)
|
|
{
|
|
// no, error, wrong skeleton
|
|
ok=InvalidSkeleton;
|
|
break;
|
|
}
|
|
|
|
// Set the weight
|
|
buildMesh.SkinWeights[vert].MatrixId[id]=matrixId;
|
|
buildMesh.SkinWeights[vert].Weights[id]=ite->first/sum;
|
|
|
|
// Next Id
|
|
id++;
|
|
}
|
|
// Breaked ?
|
|
if (ite!=weightMap.begin())
|
|
{
|
|
// break again to exit
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (SKIN_INTERFACE, comSkinInterface);
|
|
}
|
|
else
|
|
{
|
|
// ********** PHYSIQUE EXPORT **********
|
|
|
|
// Physique mode
|
|
Modifier* skin=getModifier (&node, PHYSIQUE_CLASS_ID);
|
|
|
|
// Must exist
|
|
nlassert (skin);
|
|
|
|
// Get a com_skin2 interface
|
|
IPhysiqueExport *physiqueInterface=(IPhysiqueExport *)skin->GetInterface (I_PHYINTERFACE);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (physiqueInterface);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Get local data
|
|
IPhyContextExport *localData=physiqueInterface->GetContextInterface(&node);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (localData);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Use rigid export
|
|
localData->ConvertToRigid (TRUE);
|
|
|
|
// Allow blending
|
|
localData->AllowBlending (TRUE);
|
|
|
|
// Check same vertices count
|
|
uint vertCount=localData->GetNumberVertices();
|
|
|
|
// Ctrl we have the same number of vertices in the mesh and in the modifier.
|
|
if (buildMesh.Vertices.size()!=vertCount)
|
|
{
|
|
ok=InvalidSkeleton;
|
|
}
|
|
else
|
|
{
|
|
// If not the same count, return false (perhaps, the modifier is not in the good place in the stack..)
|
|
if (buildMesh.Vertices.size()==vertCount)
|
|
{
|
|
// Rebuild the array
|
|
buildMesh.SkinWeights.resize (vertCount);
|
|
|
|
// For each vertex
|
|
for (uint vert=0; vert<vertCount; vert++)
|
|
{
|
|
// Get a vertex interface
|
|
IPhyVertexExport *vertexInterface=localData->GetVertexInterface (vert);
|
|
|
|
// Check if it is a rigid vertex or a blended vertex
|
|
IPhyRigidVertex *rigidInterface=NULL;
|
|
IPhyBlendedRigidVertex *blendedInterface=NULL;
|
|
int type=vertexInterface->GetVertexType ();
|
|
if (type==RIGID_TYPE)
|
|
{
|
|
// this is a rigid vertex
|
|
rigidInterface=(IPhyRigidVertex*)vertexInterface;
|
|
}
|
|
else
|
|
{
|
|
// It must be a blendable vertex
|
|
nlassert (type==RIGID_BLENDED_TYPE);
|
|
blendedInterface=(IPhyBlendedRigidVertex*)vertexInterface;
|
|
}
|
|
|
|
// Get bones count for this vertex
|
|
uint boneCount;
|
|
if (blendedInterface)
|
|
{
|
|
// If blenvertex, only one bone
|
|
boneCount=blendedInterface->GetNumberNodes();
|
|
}
|
|
else
|
|
{
|
|
// If rigid vertex, only one bone
|
|
boneCount=1;
|
|
}
|
|
|
|
// No bones, can't export
|
|
if (boneCount==0)
|
|
{
|
|
// Error
|
|
ok=VertexWithoutWeight;
|
|
break;
|
|
}
|
|
|
|
// A map of float / string
|
|
std::multimap<float, INode*> weightMap;
|
|
|
|
// For each bones
|
|
for (uint bone=0; bone<boneCount; bone++)
|
|
{
|
|
if (blendedInterface)
|
|
{
|
|
// Get node
|
|
INode *node=blendedInterface->GetNode(bone);
|
|
if (node == NULL)
|
|
{
|
|
nlwarning("node == NULL; bone = %i / %i", bone, boneCount);
|
|
}
|
|
else
|
|
{
|
|
// Get the bone weight
|
|
float weight=blendedInterface->GetWeight(bone);
|
|
|
|
// Insert in the map
|
|
weightMap.insert (std::map<float, INode*>::value_type (weight, node));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get node
|
|
INode *node=rigidInterface->GetNode();
|
|
nlassert (node);
|
|
|
|
// Insert in the map
|
|
weightMap.insert (std::map<float, INode*>::value_type (1, node));
|
|
}
|
|
}
|
|
|
|
// Keep only the NL3D_MESH_SKINNING_MAX_MATRIX highest bones
|
|
while (weightMap.size()>NL3D_MESH_SKINNING_MAX_MATRIX)
|
|
{
|
|
// Remove the lowest weights
|
|
weightMap.erase (weightMap.begin());
|
|
}
|
|
|
|
// Sum the NL3D_MESH_SKINNING_MAX_MATRIX highest bones
|
|
float sum=0.f;
|
|
std::multimap<float, INode*>::iterator ite=weightMap.begin();
|
|
while (ite!=weightMap.end())
|
|
{
|
|
// Add to the sum
|
|
sum+=ite->first;
|
|
|
|
// Next value
|
|
ite++;
|
|
}
|
|
|
|
// Erase bones
|
|
for (uint i=0; i<NL3D_MESH_SKINNING_MAX_MATRIX; i++)
|
|
{
|
|
// Erase
|
|
buildMesh.SkinWeights[vert].MatrixId[i]=0;
|
|
buildMesh.SkinWeights[vert].Weights[i]=0;
|
|
}
|
|
|
|
// For each bones in the list, build the skin information
|
|
uint id=0;
|
|
ite=weightMap.end();
|
|
while (ite!=weightMap.begin())
|
|
{
|
|
// Previous value
|
|
ite--;
|
|
|
|
// Get the bones ID
|
|
sint32 matrixId=-1;
|
|
TInodePtrInt::const_iterator itId=skeletonShape.find (ite->second);
|
|
if (itId!=skeletonShape.end())
|
|
matrixId=itId->second;
|
|
|
|
// Find the bone ?
|
|
if (matrixId==-1)
|
|
{
|
|
// no, error, wrong skeleton
|
|
ok=InvalidSkeleton;
|
|
break;
|
|
}
|
|
|
|
// Set the weight
|
|
buildMesh.SkinWeights[vert].MatrixId[id]=matrixId;
|
|
buildMesh.SkinWeights[vert].Weights[id]=ite->first/sum;
|
|
|
|
// Next Id
|
|
id++;
|
|
}
|
|
|
|
// Breaked ?
|
|
if (ite!=weightMap.begin())
|
|
{
|
|
// break again to exit
|
|
break;
|
|
}
|
|
|
|
// Release vertex interfaces
|
|
localData->ReleaseVertexInterface (vertexInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release locaData interface
|
|
physiqueInterface->ReleaseContextInterface (localData);
|
|
}
|
|
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (I_PHYINTERFACE, physiqueInterface);
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
INode *getRoot (INode *pNode)
|
|
{
|
|
INode* parent=pNode->GetParentNode();
|
|
if (parent)
|
|
{
|
|
if (parent->IsRootNode())
|
|
return pNode;
|
|
else
|
|
return getRoot (parent);
|
|
}
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
INode* CExportNel::getSkeletonRootBone (INode& node)
|
|
{
|
|
// Return node
|
|
INode* ret=NULL;
|
|
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, SKIN_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
ISkin *comSkinInterface=(ISkin*)skin->GetInterface (SKIN_INTERFACE);
|
|
|
|
// Found com_skin2 ?
|
|
if (comSkinInterface)
|
|
{
|
|
// Get local data
|
|
ISkinContextData *localData=comSkinInterface->GetContextInterface(&node);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Look for a bone...
|
|
|
|
// For each vertices
|
|
for (uint vtx=0; vtx<(uint)localData->GetNumPoints(); vtx++)
|
|
{
|
|
// For each bones
|
|
uint bone;
|
|
for (bone=0; bone<(uint)localData->GetNumAssignedBones (vtx); bone++)
|
|
{
|
|
// Get the bone pointer
|
|
INode *newBone=comSkinInterface->GetBone(localData->GetAssignedBone(vtx, bone));
|
|
|
|
// Get the root of the hierarchy
|
|
ret=getRoot (newBone);
|
|
break;
|
|
}
|
|
// Rebreak
|
|
if (bone!=(uint)localData->GetNumAssignedBones (vtx))
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (SKIN_INTERFACE, comSkinInterface);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get the skin modifier
|
|
skin=getModifier (&node, PHYSIQUE_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
IPhysiqueExport *physiqueInterface=(IPhysiqueExport *)skin->GetInterface (I_PHYINTERFACE);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Get local data
|
|
IPhyContextExport *localData=physiqueInterface->GetContextInterface(&node);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Use rigid export
|
|
localData->ConvertToRigid (TRUE);
|
|
|
|
// Allow blending
|
|
localData->AllowBlending (TRUE);
|
|
|
|
// Look for a bone...
|
|
|
|
// For each vertices
|
|
uint numVert=(uint)localData->GetNumberVertices();
|
|
for (uint vtx=0; vtx<numVert; vtx++)
|
|
{
|
|
bool found=false;
|
|
|
|
// Get a vertex interface
|
|
IPhyVertexExport *vertexInterface=localData->GetVertexInterface (vtx);
|
|
|
|
// Check if it is a rigid vertex or a blended vertex
|
|
int type=vertexInterface->GetVertexType ();
|
|
if (type==RIGID_TYPE)
|
|
{
|
|
// this is a rigid vertex
|
|
IPhyRigidVertex *rigidInterface=(IPhyRigidVertex*)vertexInterface;
|
|
|
|
// Get the bone
|
|
INode *newBone=rigidInterface->GetNode();
|
|
|
|
// Get the root of the hierarchy
|
|
ret=getRoot (newBone);
|
|
found=true;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
// It must be a blendable vertex
|
|
nlassert (type==RIGID_BLENDED_TYPE);
|
|
IPhyBlendedRigidVertex *blendedInterface=(IPhyBlendedRigidVertex*)vertexInterface;
|
|
|
|
// For each bones
|
|
uint bone;
|
|
uint count=(uint)blendedInterface->GetNumberNodes ();
|
|
for (bone=0; bone<count; bone++)
|
|
{
|
|
// Get the bone pointer
|
|
INode *newBone=blendedInterface->GetNode(bone);
|
|
|
|
// Get the root of the hierarchy
|
|
ret=getRoot (newBone);
|
|
found=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Release vertex interfaces
|
|
localData->ReleaseVertexInterface (vertexInterface);
|
|
|
|
// Rebreak
|
|
if (found)
|
|
break;
|
|
|
|
// Release vertex interfaces
|
|
localData->ReleaseVertexInterface (vertexInterface);
|
|
}
|
|
|
|
// Release locaData interface
|
|
physiqueInterface->ReleaseContextInterface (localData);
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (I_PHYINTERFACE, physiqueInterface);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return result;
|
|
return ret;
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
void CExportNel::addSkeletonBindPos (INode& skinedNode, mapBoneBindPos& boneBindPos)
|
|
{
|
|
// Return success
|
|
uint ok=NoError;
|
|
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&skinedNode, SKIN_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
ISkin *comSkinInterface=(ISkin*)skin->GetInterface (SKIN_INTERFACE);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (comSkinInterface);
|
|
|
|
// Found com_skin2 ?
|
|
if (comSkinInterface)
|
|
{
|
|
// Get local data
|
|
ISkinContextData *localData=comSkinInterface->GetContextInterface(&skinedNode);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (localData);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Check same vertices count
|
|
uint vertCount=localData->GetNumPoints();
|
|
|
|
// For each vertex
|
|
for (uint vert=0; vert<vertCount; vert++)
|
|
{
|
|
// Get bones count for this vertex
|
|
uint boneCount=localData->GetNumAssignedBones (vert);
|
|
|
|
// For each bones
|
|
for (uint bone=0; bone<boneCount; bone++)
|
|
{
|
|
// Get the bone id
|
|
int boneId=localData->GetAssignedBone(vert, bone);
|
|
|
|
// Get bone INode*
|
|
INode *boneNode=comSkinInterface->GetBone(boneId);
|
|
|
|
// Get the bind matrix of the bone
|
|
Matrix3 bindPos;
|
|
comSkinInterface->GetBoneInitTM(boneNode, bindPos);
|
|
|
|
// Add an entry inthe map
|
|
boneBindPos.insert (mapBoneBindPos::value_type (boneNode, bindPos));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (SKIN_INTERFACE, comSkinInterface);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&skinedNode, PHYSIQUE_CLASS_ID);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (skin);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
IPhysiqueExport *physiqueInterface=(IPhysiqueExport *)skin->GetInterface (I_PHYINTERFACE);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (physiqueInterface);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Get local data
|
|
IPhyContextExport *localData=physiqueInterface->GetContextInterface(&skinedNode);
|
|
|
|
// Should been controled with isSkin before.
|
|
nlassert (localData);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Use rigid export
|
|
localData->ConvertToRigid (TRUE);
|
|
|
|
// Allow blending
|
|
localData->AllowBlending (TRUE);
|
|
|
|
// Check same vertices count
|
|
uint vertCount=localData->GetNumberVertices();
|
|
|
|
// For each vertex
|
|
for (uint vert=0; vert<vertCount; vert++)
|
|
{
|
|
if (vert==111)
|
|
int toto=0;
|
|
// Get a vertex interface
|
|
IPhyVertexExport *vertexInterface=localData->GetVertexInterface (vert);
|
|
|
|
// Check if it is a rigid vertex or a blended vertex
|
|
int type=vertexInterface->GetVertexType ();
|
|
if (type==RIGID_TYPE)
|
|
{
|
|
// this is a rigid vertex
|
|
IPhyRigidVertex *rigidInterface=(IPhyRigidVertex*)vertexInterface;
|
|
|
|
// Get bone INode*
|
|
INode *bone=rigidInterface->GetNode();
|
|
|
|
// Get the bind matrix of the bone
|
|
Matrix3 bindPos;
|
|
int res=physiqueInterface->GetInitNodeTM (bone, bindPos);
|
|
nlassert (res==MATRIX_RETURNED);
|
|
|
|
// Add an entry inthe map
|
|
if (boneBindPos.insert (mapBoneBindPos::value_type (bone, bindPos)).second)
|
|
{
|
|
#ifdef NL_DEBUG
|
|
// *** Debug info
|
|
|
|
// Bone name
|
|
std::string boneName=getName (*bone);
|
|
|
|
// Local matrix
|
|
Matrix3 nodeTM;
|
|
nodeTM=bone->GetNodeTM (0);
|
|
|
|
// Offset matrix
|
|
Matrix3 offsetScaleTM (TRUE);
|
|
Matrix3 offsetRotTM (TRUE);
|
|
Matrix3 offsetPosTM (TRUE);
|
|
ApplyScaling (offsetScaleTM, bone->GetObjOffsetScale ());
|
|
offsetRotTM.SetRotate (bone->GetObjOffsetRot ());
|
|
offsetPosTM.SetTrans (bone->GetObjOffsetPos ());
|
|
Matrix3 offsetTM = offsetScaleTM * offsetRotTM * offsetPosTM;
|
|
|
|
// Local + offset matrix
|
|
Matrix3 nodeOffsetTM = offsetTM * nodeTM;
|
|
|
|
// Init TM
|
|
Matrix3 initTM;
|
|
int res=physiqueInterface->GetInitNodeTM (bone, initTM);
|
|
nlassert (res==MATRIX_RETURNED);
|
|
|
|
// invert
|
|
initTM.Invert();
|
|
Matrix3 compNode=nodeTM*initTM;
|
|
Matrix3 compOffsetNode=nodeOffsetTM*initTM;
|
|
Matrix3 compOffsetNode2=nodeOffsetTM*initTM;
|
|
#endif // NL_DEBUG
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// It must be a blendable vertex
|
|
nlassert (type==RIGID_BLENDED_TYPE);
|
|
IPhyBlendedRigidVertex *blendedInterface=(IPhyBlendedRigidVertex*)vertexInterface;
|
|
|
|
// For each bones
|
|
uint boneIndex;
|
|
uint count=(uint)blendedInterface->GetNumberNodes ();
|
|
for (boneIndex=0; boneIndex<count; boneIndex++)
|
|
{
|
|
// Get the bone pointer
|
|
INode *bone = blendedInterface->GetNode(boneIndex);
|
|
|
|
if (bone == NULL)
|
|
{
|
|
nlwarning("bone == NULL; boneIndex = %i / %i", boneIndex, count);
|
|
}
|
|
else
|
|
{
|
|
// Get the bind matrix of the bone
|
|
Matrix3 bindPos;
|
|
int res = physiqueInterface->GetInitNodeTM (bone, bindPos);
|
|
|
|
if (res != MATRIX_RETURNED)
|
|
{
|
|
nlwarning("res != MATRIX_RETURNED; res = %i; boneIndex = %i / %i", res, boneIndex, count);
|
|
nlwarning("bone = %i", (uint32)(void *)bone);
|
|
std::string boneName = getName (*bone);
|
|
nlwarning("boneName = %s", boneName.c_str());
|
|
nlassert(false);
|
|
}
|
|
|
|
// Add an entry inthe map
|
|
if (boneBindPos.insert (mapBoneBindPos::value_type (bone, bindPos)).second)
|
|
{
|
|
#ifdef NL_DEBUG
|
|
// *** Debug info
|
|
|
|
// Bone name
|
|
std::string boneName=getName (*bone);
|
|
|
|
// Local matrix
|
|
Matrix3 nodeTM;
|
|
nodeTM=bone->GetNodeTM (0);
|
|
|
|
// Offset matrix
|
|
Matrix3 offsetScaleTM (TRUE);
|
|
Matrix3 offsetRotTM (TRUE);
|
|
Matrix3 offsetPosTM (TRUE);
|
|
ApplyScaling (offsetScaleTM, bone->GetObjOffsetScale ());
|
|
offsetRotTM.SetRotate (bone->GetObjOffsetRot ());
|
|
offsetPosTM.SetTrans (bone->GetObjOffsetPos ());
|
|
Matrix3 offsetTM = offsetScaleTM * offsetRotTM * offsetPosTM;
|
|
|
|
// Local + offset matrix
|
|
Matrix3 nodeOffsetTM = offsetTM * nodeTM;
|
|
|
|
// Init TM
|
|
Matrix3 initTM;
|
|
int res=physiqueInterface->GetInitNodeTM (bone, initTM);
|
|
nlassert (res==MATRIX_RETURNED);
|
|
|
|
// invert
|
|
initTM.Invert();
|
|
Matrix3 compNode=nodeTM*initTM;
|
|
Matrix3 compOffsetNode=nodeOffsetTM*initTM;
|
|
Matrix3 compOffsetNode2=nodeOffsetTM*initTM;
|
|
#endif // NL_DEBUG
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Release vertex interfaces
|
|
localData->ReleaseVertexInterface (vertexInterface);
|
|
}
|
|
|
|
// Release locaData interface
|
|
physiqueInterface->ReleaseContextInterface (localData);
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (SKIN_INTERFACE, physiqueInterface);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
|
|
|
|
void CExportNel::enableSkinModifier (INode& node, bool enable)
|
|
{
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, SKIN_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Enable ?
|
|
if (enable)
|
|
skin->EnableMod ();
|
|
else
|
|
skin->DisableMod ();
|
|
}
|
|
else
|
|
{
|
|
// Get the physique modifier
|
|
Modifier* skin=getModifier (&node, PHYSIQUE_CLASS_ID);
|
|
|
|
// Found it ?
|
|
if (skin)
|
|
{
|
|
// Enable ?
|
|
if (enable)
|
|
skin->EnableMod ();
|
|
else
|
|
skin->DisableMod ();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ***************************************************************************
|
|
#define TEMP_MAX_WEIGHT 8
|
|
struct CTempSkinVertex
|
|
{
|
|
// World Position. TODO: world,local????
|
|
NLMISC::CVector Pos;
|
|
// The number of weight. TODO: more
|
|
uint NumWeight;
|
|
INode *Bone[TEMP_MAX_WEIGHT];
|
|
float Weight[TEMP_MAX_WEIGHT];
|
|
// If this vertex belongs to the input (=> not be modified)
|
|
bool Input;
|
|
// If this vertex is an output modified related to mirroring (NB: only
|
|
bool Mirrored;
|
|
|
|
CTempSkinVertex()
|
|
{
|
|
NumWeight= 0;
|
|
Input= false;
|
|
Mirrored= false;
|
|
}
|
|
};
|
|
|
|
struct CSortVertex
|
|
{
|
|
uint Index;
|
|
float SqrDist;
|
|
bool operator<(const CSortVertex &o) const
|
|
{
|
|
return SqrDist < o.SqrDist;
|
|
}
|
|
};
|
|
|
|
// get the bone Side -1,0,1
|
|
static sint getBoneSide(INode *bone, std::string &mirrorName)
|
|
{
|
|
sint side= 0;
|
|
sint pos;
|
|
mirrorName= bone->GetName();
|
|
|
|
if((pos= mirrorName.find(" R "))!=std::string::npos)
|
|
{
|
|
side= 1;
|
|
mirrorName[pos+1]= 'L';
|
|
}
|
|
else if((pos= mirrorName.find(" L "))!=std::string::npos)
|
|
{
|
|
side= -1;
|
|
mirrorName[pos+1]= 'R';
|
|
}
|
|
|
|
return side;
|
|
}
|
|
|
|
// From a bone, retrieve the bone mirror (by name: R / L). NB: if not found, return same (eg: important for mirror on spine).
|
|
static INode *getMirrorBone(const std::vector<INode*> &skeletonNodes, INode *bone)
|
|
{
|
|
// TODO: optimize doing a map bone / mirrored bone
|
|
std::string mirrorName;
|
|
sint bs= getBoneSide(bone, mirrorName);
|
|
|
|
// if not a middle bone
|
|
if(bs!=0)
|
|
{
|
|
// find
|
|
for(uint i=0;i<skeletonNodes.size();i++)
|
|
{
|
|
if(mirrorName == skeletonNodes[i]->GetName())
|
|
return skeletonNodes[i];
|
|
}
|
|
}
|
|
|
|
// if fails, return him
|
|
return bone;
|
|
}
|
|
|
|
bool CExportNel::mirrorPhysiqueSelection(INode &node, TimeValue tvTime, const std::vector<uint> &vertIn,
|
|
float threshold)
|
|
{
|
|
bool ok;
|
|
uint i;
|
|
|
|
// no vertices selected?
|
|
if(vertIn.empty())
|
|
return true;
|
|
|
|
// **** Get all the skeleton node
|
|
std::vector<INode*> skeletonNodes;
|
|
INode *skelRoot= getSkeletonRootBone(node);
|
|
if(!skelRoot)
|
|
return false;
|
|
getObjectNodes(skeletonNodes, tvTime, skelRoot);
|
|
|
|
|
|
// **** Build the Vector (world) part
|
|
std::vector<CTempSkinVertex> tempVertex;
|
|
uint vertCount;
|
|
|
|
// Get a pointer on the object's node.
|
|
ObjectState os = node.EvalWorldState(tvTime);
|
|
Object *obj = os.obj;
|
|
|
|
// Check if there is an object
|
|
ok= false;
|
|
if (obj)
|
|
{
|
|
|
|
// Object can be converted in triObject ?
|
|
if (obj->CanConvertToType(Class_ID(TRIOBJ_CLASS_ID, 0)))
|
|
{
|
|
// Get a triobject from the node
|
|
TriObject *tri = (TriObject*)obj->ConvertToType(tvTime, Class_ID(TRIOBJ_CLASS_ID, 0));
|
|
|
|
if (tri)
|
|
{
|
|
// Note that the TriObject should only be deleted
|
|
// if the pointer to it is not equal to the object
|
|
// pointer that called ConvertToType()
|
|
bool deleteIt=false;
|
|
if (obj != tri)
|
|
deleteIt = true;
|
|
|
|
// Get the node matrix. TODO: Matrix headhache?
|
|
/*Matrix3 nodeMatrixMax;
|
|
CMatrix nodeMatrix;
|
|
getLocalMatrix (nodeMatrixMax, node, tvTime);
|
|
convertMatrix (nodeMatrix, nodeMatrixMax);*/
|
|
|
|
// retrive Position geometry
|
|
vertCount= tri->NumPoints();
|
|
tempVertex.resize(vertCount);
|
|
for(uint i=0;i<vertCount;i++)
|
|
{
|
|
Point3 v= tri->GetPoint(i);
|
|
tempVertex[i].Pos.set(v.x, v.y, v.z);
|
|
}
|
|
|
|
// Delete the triObject if we should...
|
|
if (deleteIt)
|
|
tri->MaybeAutoDelete();
|
|
tri = NULL;
|
|
|
|
// ok!
|
|
ok= true;
|
|
}
|
|
}
|
|
}
|
|
if(!ok)
|
|
return false;
|
|
|
|
// no vertices? abort
|
|
if(vertCount==0)
|
|
return true;
|
|
|
|
|
|
// **** Mark all Input vertices
|
|
for(i=0;i<vertIn.size();i++)
|
|
{
|
|
nlassert(vertIn[i]<vertCount);
|
|
tempVertex[vertIn[i]].Input= true;
|
|
}
|
|
|
|
|
|
// **** Build the output vertices
|
|
std::vector<uint> vertOut;
|
|
vertOut.reserve(tempVertex.size());
|
|
|
|
// Build the in bbox
|
|
CAABBox bbox;
|
|
bbox.setCenter(tempVertex[vertIn[0]].Pos);
|
|
for(i=0;i<vertIn.size();i++)
|
|
{
|
|
bbox.extend(tempVertex[vertIn[i]].Pos);
|
|
}
|
|
bbox.setHalfSize(bbox.getHalfSize()+CVector(threshold, threshold, threshold));
|
|
|
|
// mirror in X
|
|
CVector vMin= bbox.getMin();
|
|
CVector vMax= bbox.getMax();
|
|
vMin.x= -vMin.x;
|
|
vMax.x= -vMax.x;
|
|
std::swap(vMin.x, vMax.x);
|
|
bbox.setMinMax(vMin, vMax);
|
|
|
|
// get all out vertices in the mirrored bbox.
|
|
for(i=0;i<tempVertex.size();i++)
|
|
{
|
|
if(bbox.include(tempVertex[i].Pos))
|
|
{
|
|
vertOut.push_back(i);
|
|
}
|
|
}
|
|
|
|
|
|
// **** Build the skin information
|
|
// Get the skin modifier
|
|
Modifier* skin=getModifier (&node, PHYSIQUE_CLASS_ID);
|
|
|
|
// Found it ?
|
|
ok= false;
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
IPhysiqueExport *physiqueInterface=(IPhysiqueExport *)skin->GetInterface (I_PHYINTERFACE);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Get local data
|
|
IPhyContextExport *localData= physiqueInterface->GetContextInterface(&node);
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Use rigid export
|
|
localData->ConvertToRigid (TRUE);
|
|
|
|
// Allow blending
|
|
localData->AllowBlending (TRUE);
|
|
|
|
// Skinned
|
|
ok=true;
|
|
|
|
// TODO?
|
|
nlassert(tempVertex.size()<=(uint)localData->GetNumberVertices());
|
|
|
|
// For each vertex
|
|
for (uint vert=0; vert<vertCount; vert++)
|
|
{
|
|
// Get a vertex interface
|
|
IPhyVertexExport *vertexInterface= localData->GetVertexInterface (vert);
|
|
|
|
// Check if it is a rigid vertex or a blended vertex
|
|
IPhyRigidVertex *rigidInterface=NULL;
|
|
IPhyBlendedRigidVertex *blendedInterface=NULL;
|
|
int type=vertexInterface->GetVertexType ();
|
|
if (type==RIGID_TYPE)
|
|
{
|
|
// this is a rigid vertex
|
|
rigidInterface=(IPhyRigidVertex*)vertexInterface;
|
|
}
|
|
else
|
|
{
|
|
// It must be a blendable vertex
|
|
nlassert (type==RIGID_BLENDED_TYPE);
|
|
blendedInterface=(IPhyBlendedRigidVertex*)vertexInterface;
|
|
}
|
|
|
|
// Get bones count for this vertex
|
|
uint boneCount;
|
|
if (blendedInterface)
|
|
{
|
|
// If blenvertex, only one bone
|
|
boneCount=blendedInterface->GetNumberNodes();
|
|
}
|
|
else
|
|
{
|
|
// If rigid vertex, only one bone
|
|
boneCount=1;
|
|
}
|
|
if(boneCount>TEMP_MAX_WEIGHT)
|
|
boneCount= TEMP_MAX_WEIGHT;
|
|
|
|
// NB: if input 0, won't be mirrored
|
|
tempVertex[vert].NumWeight= boneCount;
|
|
for(uint bone=0;bone<boneCount;bone++)
|
|
{
|
|
if (blendedInterface)
|
|
{
|
|
tempVertex[vert].Bone[bone]= blendedInterface->GetNode(bone);
|
|
nlassert(tempVertex[vert].Bone[bone]);
|
|
tempVertex[vert].Weight[bone]= blendedInterface->GetWeight(bone);
|
|
}
|
|
else
|
|
{
|
|
tempVertex[vert].Bone[bone]= rigidInterface->GetNode();
|
|
tempVertex[vert].Weight[bone]= 1;
|
|
}
|
|
}
|
|
|
|
// Release vertex interfaces
|
|
localData->ReleaseVertexInterface (vertexInterface);
|
|
}
|
|
|
|
}
|
|
|
|
// release context interface
|
|
physiqueInterface->ReleaseContextInterface(localData);
|
|
}
|
|
|
|
// Release the interface
|
|
skin->ReleaseInterface (I_PHYINTERFACE, physiqueInterface);
|
|
}
|
|
if(!ok)
|
|
return false;
|
|
|
|
|
|
// **** Real Algo stuff:
|
|
// For all vertices wanted to be mirrored
|
|
std::vector<CSortVertex> sortVert;
|
|
sortVert.reserve(tempVertex.size());
|
|
for(i=0;i<vertIn.size();i++)
|
|
{
|
|
CTempSkinVertex &svIn= tempVertex[vertIn[i]];
|
|
// if it still has no bones set, skip
|
|
if(svIn.NumWeight==0)
|
|
continue;
|
|
|
|
// mirror vert to test
|
|
CVector vertTest= svIn.Pos;
|
|
vertTest.x*= -1;
|
|
|
|
// get the best vertex
|
|
sortVert.clear();
|
|
|
|
// Search for all output vertices if ones match
|
|
for(uint j=0;j<vertOut.size();j++)
|
|
{
|
|
uint dstIdx= vertOut[j];
|
|
nlassert(dstIdx<tempVertex.size());
|
|
CTempSkinVertex &skinv= tempVertex[dstIdx];
|
|
// take only if not an input, and if not already mirrored
|
|
if(!skinv.Input && !skinv.Mirrored)
|
|
{
|
|
CSortVertex sortv;
|
|
sortv.Index= dstIdx;
|
|
sortv.SqrDist= (skinv.Pos - vertTest).sqrnorm();
|
|
// Finally, take it only if sufficiently near
|
|
if(sortv.SqrDist <= threshold*threshold)
|
|
sortVert.push_back(sortv);
|
|
}
|
|
}
|
|
|
|
// if some found.
|
|
if(!sortVert.empty())
|
|
{
|
|
// sort array.
|
|
std::sort(sortVert.begin(), sortVert.end());
|
|
|
|
// take the first, mirror setup
|
|
uint dstIdx= sortVert[0].Index;
|
|
tempVertex[dstIdx].NumWeight= svIn.NumWeight;
|
|
for(uint k=0;k<svIn.NumWeight;k++)
|
|
{
|
|
tempVertex[dstIdx].Weight[k]= svIn.Weight[k];
|
|
tempVertex[dstIdx].Bone[k]= getMirrorBone( skeletonNodes, svIn.Bone[k] );
|
|
}
|
|
|
|
// mark as mirrored!
|
|
tempVertex[dstIdx].Mirrored= true;
|
|
}
|
|
}
|
|
|
|
|
|
// **** Write the result to the skin.
|
|
ok= false;
|
|
if (skin)
|
|
{
|
|
// Get a com_skin2 interface
|
|
IPhysiqueImport *physiqueInterface=(IPhysiqueImport *)skin->GetInterface (I_PHYIMPORT);
|
|
|
|
// Found com_skin2 ?
|
|
if (physiqueInterface)
|
|
{
|
|
// Get local data
|
|
IPhyContextImport *localData= physiqueInterface->GetContextInterface(&node);
|
|
|
|
// TODO?
|
|
nlassert(tempVertex.size()<=(uint)localData->GetNumberVertices());
|
|
|
|
// Found ?
|
|
if (localData)
|
|
{
|
|
// Skinned
|
|
ok=true;
|
|
|
|
for(uint i=0;i<tempVertex.size();i++)
|
|
{
|
|
CTempSkinVertex &sv= tempVertex[i];
|
|
|
|
// if its a mirrored output vertex
|
|
if(sv.Mirrored)
|
|
{
|
|
IPhyBlendedRigidVertexImport *blendedInterface= NULL;
|
|
blendedInterface= (IPhyBlendedRigidVertexImport*)localData->SetVertexInterface(i, RIGID_BLENDED_TYPE);
|
|
|
|
if(blendedInterface)
|
|
{
|
|
// set the vertex data
|
|
for(uint bone=0;bone<sv.NumWeight;bone++)
|
|
{
|
|
blendedInterface->SetWeightedNode(sv.Bone[bone], sv.Weight[bone], bone==0);
|
|
}
|
|
|
|
// UI bonus: lock it
|
|
blendedInterface->LockVertex(TRUE);
|
|
|
|
// release
|
|
localData->ReleaseVertexInterface(blendedInterface);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// release
|
|
physiqueInterface->ReleaseContextInterface(localData);
|
|
}
|
|
|
|
// release
|
|
skin->ReleaseInterface(I_PHYIMPORT, physiqueInterface);
|
|
}
|
|
|
|
|
|
return ok;
|
|
}
|
|
|
|
|