游戏动画引擎 -(Crapell Game Engine Design - animation)
动画重定向和逆向运动学:
Biovision Hierarchy, 是BioVision公司推出的人体动作捕捉文件格式,天然具有重定向功能。首先实现BVHSkeleton类来加载bvh文件(包含骨架和动画),与渲染引擎中的RendSys::Skeleton相互转化即可方便的实现游戏的动画重定向功能。
体型Tpose数据用只有骨架没有动画的BVHSkeleton来表示。
重定向过程BVHSkeleton1 + Tpose1 + Tpose2=> BVHSkeleton2 源动画+源姿态+目标姿态 产出目标动画。
BVHSkeleton动画文件格式为.bvh,Tpose文件格式为.bvh(0帧动画),游戏动画格式为.bone。
逆向运动学动画:使用FABRIK算法,不需要计算旋转,只需要计算线性位置,快速并且变化光滑,不会存在断点或者突变。
SkinFit类用来动态调整蒙皮和动画。
//========================================================
// @Date: 2016.05
// @File: Include/Render/BVHSkeleton.h
// @Brief: 动画重定向
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once#ifndef _BVH_H_
#define _BVH_H_#include "Math/Mathlib.h"
#include "Render/Texture.h"//3Dmax Biped骨骼要在绑定模型之前导入BVH骨骼,才会匹配BVH骨骼大小,然后可以导出bip文件
//3Dmax Biped骨骼在绑定模型之后导入bip文件,不会改变骨骼大小(3Dmax内部做了动画重定向)#include <vector>
#include <string>
using namespace std;enum ChannelType
{Xposition,Yposition,Zposition,Xrotation,Yrotation,Zrotation,
};
const char* ChannelTypeToString(int enumeration);
bool StringToChannelType(const char* str,int& type);struct RayCollideRes;
namespace RendSys
{class Skeleton;class MixSkeleton;class Mesh;class MovieClip;
}struct Channel;struct BJoint
{BJoint();string name; int index; BJoint* parent; vector<BJoint*> children; vec3 offset; //根节点初始位置及相对位置bool isSite; //是否是末节点vec3 site; //末端位置 末端节点才有 End Sitevector<Channel*> channels; //通道vector<mat4> keyMotion; //offset*trans*rot,先旋转后平移mat4 TposeRot; //mat3即可mat4 TposeRotInv;bool bMount; //不随父骨骼旋转 比如 脚印 挂载点mat4 matFinal; //vector<mat4> ?
};struct Channel
{ChannelType type; int index;
};//骨架结构 不固定 名字也不统一
//0Hips
//| 1Spine
// | 2Spine1
// | 3Neck
// | 4Head
// 5LeftShoulder
// | 6LeftArm
// | 7LeftForeArm
// | 8LeftHand
// 9RightShoulder
// | 10RightArm
// | 11RightForeArm
// | 12RightHand
// 13LeftUpLeg
// | 14LeftLeg
// | 15LeftFoot
// | 16LeftToeBase
// 17RightUpLeg
// | 18RightLeg
// | 19RightFoot
// | 20RightToeBase
//
// ●head
// |
// ●neck
// leftCollar● | ●rightCollar
// ╱╲ | ╱╲
// ╱ ╲| ╱ ╲
// leftUpArm● ●chest ●rightUpArm
// ╱ | ╲
// ╱ | ╲
// leftLowAnn● | ●rightLowArm
// ╱ | ╲
// leftHand● | ●rightHand
// ●1hips
// ╱╲
// ╱ ╲
// leftUpLeg● ●rightUpLeg
// ╱ ╲
// ╱ ╲
// leftLowLeg● ●rightLowLeg
// ╱ ╲
// ╱ ╲
// leftFoot● ●rightFoot//Biovision Hierarchy, 是BioVision公司推出的人体动作捕捉文件格式//捕捉类型:
//局部捕捉
//全身捕捉
//多人捕捉
//多人混合捕捉//捕捉方法:
//光学捕捉 一般是多目相机
//惯性捕捉 传感器
//外骨骼捕捉 机械旋转typedef vector<int> BChain;class BVHSkeleton
{
public:BVHSkeleton();~BVHSkeleton();void Free();//从文件加载,LoadFromFile("human2Tpose.bvh") bool LoadFromFile(const char* fileName);void SaveToFile(const char *fileName);void operator=(BVHSkeleton& bvh);//从3dsmax导出的游戏动画生成bvh姿态,可以预计算保存到文件 GenTposeFromSkeleton("humanTpose.bone") ->SaveToFile("humanTpose.bvh")bool GenTposeFromSkeleton(RendSys::Skeleton* skeleton);//从3dsmax导出的游戏动画生成bvh动画, GenAnimFromSkeleton("humanRun.bone","humanTpose.bvh") 可以预计算保存到文件bool GenAnimFromSkeleton(RendSys::Skeleton* anim,BVHSkeleton* Tpose);//逐帧拷贝到游戏动画void CopyToSkeleton (RendSys::Skeleton* anim);//需要事先K好帧void CopyToSkeleton2 (RendSys::Skeleton* anim);//单帧实时拷贝void CopyToMixSkeleton(RendSys::MixSkeleton* anim,float weight=1);BJoint* IntersectTray(RayCollideRes& res);void Render();//void RecursRender(Joint_ *part);void Update();void Forward();void CalKeyFrame(int frame=-1);//缩放TPose 骨架void ScaleJoints(float scale);//获取int GetJointNum() const;BJoint* GetJoint(int index) const;BJoint* GetJoint(const char* name) const;int GetChannelNum() const;//int GetFrameNum() const;float GetFps() const;//int GetChainNum() const;vector<int>& GetChain(int index);//从srcBVH中抽取动画void AnimRetargetingFrom(BVHSkeleton& srcBVH,float scale=1);//将动画赋给目标姿态void AnimRetargetingWith(BVHSkeleton& dstTpose,float scale=1);void ResizeMotion(int frameNum, int channelNum);float GetMotion(int frame, int c) const;void SetMotion(int frame, int c, float value);//猜测匹配的骨骼: id顺序一致时无需此函数 不一致时说明骨架结构不同int GuessJoint(const char* name);void Inversekinematic();
private:int m_channelNum; //通道总数vector<Channel*> m_channels; //所有通道vector<BJoint*> m_sortJoints; //所有关节 拓扑排序vector<BChain> m_chains; //骨骼链 从root到每个leaf 用于雅克比int m_curFrame;int m_frameNum; //帧数float m_ifps; //频率float* m_motions; //运动数据 一般只含旋转数据 骨骼长度在pose里 也可以包括旋转后的平移比如伸缩臂private:BVHSkeleton(BVHSkeleton&);
};//一般的游戏, skin直接在max中预先调整, 重定向的tpose也是预生成的,无需SkinFit类
//对于可以捏制体型的游戏, 才需要用到SkinFit
//SkinFit用来修改bonemovie中的蒙皮skin,以及重定向Skeleton
class SkinFit
{
public:SkinFit();~SkinFit();void Free();bool Init(BVHSkeleton* tpose);bool LoadFromFile(const char* fileName);void SaveToFile(const char *fileName);void Reset();//叠加修改void Add(SkinFit& delta);//修改蒙皮void FitSkin(RendSys::MovieClip* movie);void FitSkin(RendSys::Mesh* mesh);//修改姿态void FitTpose(BVHSkeleton* tpose);struct BoneFit{int boneID;vec3 skinScale; //skinScale.xy影响skin肥瘦, skinScale.z影响skin长短 //有时候骨骼初始方向为boneScale.x 不是boneScale.zvec3 boneScale; //只用到了boneScale.z, boneScale.z和skinScale.z通常缩放系数不同mat4 rot; //boneScale.z 和 rot(相对于体模式tpose)共同影响bvh中的骨骼偏移offset};int m_boneNum;BoneFit* m_boneFits;
};#endif
//========================================================
// @Date: 2016.05
// @File: SourceLib/Render/BVHSkeleton.cpp
// @Brief: 动画重定向
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#include "General/Pch.h"
#include "General/File.h"
#include "General/Timer.h"
#include "Render/Curve.h"
#include "Render/RendDriver.h"
#include "Render/BVHSkeleton.h"
#include "Render/MC_MovieClip.h"
#include "Render/MC_Misc.h"
#include <vector>
#include <string>
#include "General/Pce.h"
#include "Math/MathLibAdvance.h"
#include "General/StringUtil.h"
#include "Math/MathLibIncDbg.h"using namespace RendSys;
#define BUFFER_LENGTH 1024*4const char* ChannelTypeToString(int enumeration)
{switch(enumeration){case Xposition: return "Xposition";case Yposition: return "Yposition";case Zposition: return "Zposition";case Xrotation: return "Xrotation";case Yrotation: return "Yrotation";case Zrotation: return "Zrotation";}return "";
}
bool StringToChannelType(const char* str,int& type)
{if (stricmp(str,"Xposition")==0){ type = Xposition; return true;}else if (stricmp(str,"Yposition")==0){ type = Yposition; return true;}else if (stricmp(str,"Zposition")==0){ type = Zposition; return true;}else if (stricmp(str,"Xrotation")==0){ type = Xrotation; return true;}else if (stricmp(str,"Yrotation")==0){ type = Yrotation; return true;}else if (stricmp(str,"Zrotation")==0){ type = Zrotation; return true;}type = Xposition;return false;
}BJoint::BJoint()
:parent(NULL)
,bMount(false)
{}BVHSkeleton::BVHSkeleton()
{m_channelNum = 0;m_frameNum = 0;m_ifps = 0;m_motions = NULL;m_curFrame = 0;
}
BVHSkeleton::~BVHSkeleton()
{Free();
}
void BVHSkeleton::Free()
{for(int i = 0; i != m_channels.size(); ++i)delete m_channels[i];m_channels.clear();for(int i = 0; i != m_sortJoints.size(); ++i)delete m_sortJoints[i];m_sortJoints.clear();SafeDeleteArray(m_motions);m_chains.clear();m_channelNum = 0;m_frameNum = 0;m_ifps = 0;
}
bool BVHSkeleton::LoadFromFile(const char* fileName)
{Free();File file;char token[256];vector<BJoint*> jointStack;BJoint* joint = NULL;BJoint* newJoint = NULL;if(file.Fopen(fileName,"rt") == false)return false; file.SetSplitChar(" ,[]\n\x0a ()=:;|\x0d");while(1){if(file.IsEnd())return false;file.ReadBlock(token,256);//关节的开始if(strcmp(token, "{") == 0){//当前关节添加到栈中jointStack.push_back(joint);joint = newJoint;continue;}//关节的结束if(strcmp(token, "}") == 0){//释放栈中当前关节joint = jointStack.back();jointStack.pop_back();//if(joint->isSite==false)// joint = joint->parent; //末端节点退出两次处理不同continue;}//判断关节if((strcmp(token, "ROOT") == 0) || strcmp(token, "JOINT") == 0){//创建关节newJoint = new BJoint();newJoint->index = m_sortJoints.size();newJoint->parent = joint;newJoint->isSite = false;m_sortJoints.push_back(newJoint);if(joint)joint->children.push_back(newJoint);//获取关节名file.SetSplitChar("\n");file.ReadBlock(token,256);file.SetSplitChar(" ,[]\n\x0a ()=:;|\x0d");newJoint->name = token;if (StriStr(newJoint->name.c_str(),"footstep")){newJoint->bMount = true;}continue;}//末端 End Siteif((strcmp(token, "End") == 0)){newJoint = joint;// End Site 并不新建骨骼joint->isSite = true;file.NextLine();continue;}//偏移量或者末端位置if((strcmp(token, "OFFSET") == 0)){//坐标的偏移量if(joint->isSite){file.ReadFloatArray(&joint->site.x,3);}else{file.ReadFloatArray(&joint->offset.x,3);}continue;}//关节的通道if(strcmp(token, "CHANNELS") == 0){//通道数量int num = file.ReadInt();joint->channels.resize(num);for(int i = 0; i != num; ++i){//创建通道Channel* channel = new Channel;channel->index = m_channels.size();m_channels.push_back(channel);joint->channels[i] = channel;//获取通道类别file.ReadBlock(token,256);int type;bool res = StringToChannelType(token,type);channel->type = (ChannelType)type;}}//运动数据段前停止if(strcmp(token, "MOTION") == 0)break;}//topology sorted//遍历骨骼链BChain chain;vector<int> invChain;for(int f = 1; f != m_sortJoints.size(); ++f){if(m_sortJoints[f]->isSite){BJoint* joint = m_sortJoints[f];invChain.push_back(joint->index);while (joint->parent){joint = joint->parent;invChain.push_back(joint->index);}//逆序while(invChain.size() != 0){chain.push_back(invChain.back());invChain.pop_back();}m_chains.push_back(chain);chain.clear();}}//编辑运动file.ReadBlock(token,256);if(strcmp(token, "Frames") != 0)return false;m_frameNum = file.ReadInt();//"Frame Time"file.ReadBlock(token,256);if(strcmp(token, "Frame") != 0)return false;file.ReadBlock(token,256);if(strcmp(token, "Time") != 0)return false;m_ifps = file.ReadFloat();m_channelNum = m_channels.size();//数据获取m_motions = new float[m_frameNum * m_channelNum];for(int f = 0; f < m_frameNum; ++f){Channel** channel = &m_channels[0];for(int j = 0; j < m_channelNum; ++j){switch((*channel)->type){case(Xposition):case(Yposition):case(Zposition):m_motions[f * m_channelNum + j] = file.ReadFloat();break;case(Zrotation):case(Yrotation):case(Xrotation):m_motions[f * m_channelNum + j] = file.ReadFloat()*DEG2RAD;break;}channel++;}}//读取bvh 扩展file.ReadBlock(token,256);if(strcmp(token, "EXTROT") == 0){for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];file.ReadFloatArray(joint->TposeRot.mat,16);joint->TposeRotInv = joint->TposeRot;joint->TposeRotInv.Inverse();}}file.Fclose();CalKeyFrame();return true;
}static void WriteJoint(File& outfile, BJoint *joint,string space)
{
#define SPACE outfile.Fprintf(space.c_str());if (joint->parent==NULL){SPACE outfile.Fprintf("ROOT %s\n",joint->name.c_str());}else{SPACE outfile.Fprintf("JOINT %s\n",joint->name.c_str());}SPACE outfile.Fprintf("{\n");string space_ = space;space += " ";float* a = joint->offset;SPACE outfile.Fprintf("OFFSET %f %f %f\n",a[0],a[1],a[2]);SPACE outfile.Fprintf("CHANNELS %d ",int(joint->channels.size()));for(int i = 0; i != (joint->channels).size(); ++i){/*SPACE */outfile.Fprintf(ChannelTypeToString(joint->channels[i]->type));/*SPACE*/ outfile.Fprintf(" ");}outfile.Fprintf("\n");if(joint->children.size() != 0){for(int i = 0; i != joint->children.size(); ++i)WriteJoint(outfile, joint->children[i],space);}if(joint->isSite == true){SPACE outfile.Fprintf("End Site\n");SPACE outfile.Fprintf("{\n");a = joint->site;SPACE outfile.Fprintf("OFFSET %f %f %f\n",a[0],a[1],a[2]);SPACE outfile.Fprintf("}\n");}outfile.Fprintf(space_.c_str()); outfile.Fprintf("}\n");
}void BVHSkeleton::SaveToFile(const char *fileName)
{BJoint *joint = GetJoint(0);if (joint==NULL){return;}File outfile;if(outfile.Fopen(fileName,"wt")==false){return;}outfile.Fprintf("HIERARCHY\n");WriteJoint(outfile, joint,"");outfile.Fprintf("MOTION\n");outfile.Fprintf("Frames: %d\n",m_frameNum);outfile.Fprintf("Frame Time: %f\n",m_ifps);for(int f = 0; f != m_frameNum; ++f){int start = f*m_channelNum;Channel** channel = &m_channels[0];for(int c = 0; c != m_channelNum; ++c){switch((*channel)->type){case(Xposition):case(Yposition):case(Zposition):outfile.Fprintf(" %f",m_motions[start + c]);break;case(Zrotation):case(Yrotation):case(Xrotation):outfile.Fprintf(" %f",m_motions[start + c]*RAD2DEG);break;}channel++;}outfile.Fprintf("\n");}//保存bvh 扩展outfile.Fprintf("EXTROT\n");for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];outfile.WriteFloatArray(joint->TposeRot.mat,16);outfile.Fprintf("\n");}outfile.Fclose();return ;
}void BVHSkeleton::Render()
{if (m_sortJoints.size()==0 || m_frameNum==0){return ;}G_RendDriver->DisableRendState(RS_TEXTURE_2D);G_RendDriver->SetLineWidth(1);mat4 temp;mat4 model;for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];BJoint* parentJoint = joint->parent;if (parentJoint/* && parentJoint->flag==1*/){if (joint->bMount==false){float length = (parentJoint->matFinal.GetTranslate() - joint->matFinal.GetTranslate()).Length();temp.FromScale(vec3(length,1, 1));model = parentJoint->matFinal * temp;G_RendDriver->PushMatrix();G_RendDriver->MultMatrix(model);G_RendDriver->Color4f(0, 0, 1, 0.5f);Skeleton::EditRendIdentityBone();G_RendDriver->Color3f(0.0f, 1.0f, 0.0f);G_RendDriver->RendBegin(RS_LINES);G_RendDriver->Vertex3f(0, 0, 0);G_RendDriver->Vertex3f(1,0,0);G_RendDriver->RendEnd();G_RendDriver->SetPointSize(2);G_RendDriver->Color3f(1.0f, 0.0f, 0.0f);G_RendDriver->RendBegin(RS_POINTS);G_RendDriver->Vertex3f(0, 0, 0);G_RendDriver->Vertex3f(1,0,0);G_RendDriver->RendEnd();G_RendDriver->PopMatrix();}else{temp.FromScale(vec3(1,1, 1));model = joint->matFinal * temp;G_RendDriver->PushMatrix();G_RendDriver->MultMatrix(model);G_RendDriver->Color4f(0, 0, 1, 0.5f);Skeleton::EditRendIdentityBone();G_RendDriver->Color3f(0.0f, 1.0f, 0.0f);G_RendDriver->RendBegin(RS_LINES);G_RendDriver->Vertex3f(0, 0, 0);G_RendDriver->Vertex3f(1,0,0);G_RendDriver->RendEnd();G_RendDriver->SetPointSize(2);G_RendDriver->Color3f(1.0f, 0.0f, 0.0f);G_RendDriver->RendBegin(RS_POINTS);G_RendDriver->Vertex3f(0, 0, 0);G_RendDriver->Vertex3f(1,0,0);G_RendDriver->RendEnd();G_RendDriver->PopMatrix();}}}
}void BVHSkeleton::Update()
{//return;if (m_sortJoints.size()==0 || m_frameNum==0){return;}static float frame = 1;frame += G_Timer->GetStepTime()/m_ifps;if(frame >= m_frameNum)frame = 0;m_curFrame = frame;Forward();
}BJoint* BVHSkeleton::IntersectTray(RayCollideRes& res)
{if (m_sortJoints.size()==0){return NULL;}vec3 a1, b1, a2, b2;float s, t;vec3 resPos1, resPos2;b1 = res.vStart;b2 = res.vEnd;BJoint* nearJoint = NULL;float nearDist = 1;for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];BJoint* parentJoint = joint->parent;if (parentJoint/* && parentJoint->flag==1*/){if (joint->bMount==false){a1 = parentJoint->matFinal.GetTranslate();a2 = joint->matFinal.GetTranslate();}else{a1 = joint->matFinal.GetTranslate();a2 = joint->matFinal * vec3(1,0,0);}ClosestSegmentSegment(a1, a2, b1, b2, s, t, resPos1, resPos2);float dist = (resPos1-resPos2).Length();if (dist < nearDist){nearJoint = joint;nearDist = dist;}}}return nearJoint;
}void BVHSkeleton::Forward()
{//int Size = m_sortJoints.size();BJoint** ppJoint = &m_sortJoints[0];for (int i=0;i<Size;++i,++ppJoint){BJoint* joint = *ppJoint;//root :先绕原点旋转再平移到root关键帧位置 keyMotion包含位置 offset为0//子骨骼:先绕父骨骼起点旋转再平移到父骨骼的末端 keyMotion一般只含旋转 offset不为0mat4& mat = joint->keyMotion[m_curFrame]; if (joint->parent){//joint->matFinal = joint->parent->matFinal * mat;Mat4xMat4(joint->matFinal,joint->parent->matFinal,mat);}else{joint->matFinal = mat;}}//!!!ppJoint = &m_sortJoints[0];for (int i=0;i<Size;++i,++ppJoint){BJoint* joint = *ppJoint;joint->matFinal *= joint->TposeRot;}
}void BVHSkeleton::operator=(BVHSkeleton& bvh)
{Free();m_channelNum = bvh.m_channelNum; m_channels.resize(m_channelNum);for (int i=0;i<m_channelNum;i++){m_channels[i] = new Channel;*m_channels[i] = *bvh.m_channels[i];}m_sortJoints.resize(bvh.m_sortJoints.size());for (int i=0;i<m_sortJoints.size();i++){BJoint* dst = new BJoint;m_sortJoints[i] = dst;BJoint* src = bvh.m_sortJoints[i];dst->name = src->name ;dst->index = src->index ;dst->offset = src->offset ;dst->isSite = src->isSite ;dst->site = src->site ;dst->keyMotion = src->keyMotion ;dst->matFinal = src->matFinal ;dst->TposeRot = src->TposeRot ;dst->TposeRotInv = src->TposeRotInv ;dst->bMount = src->bMount;for (int j=0;j<src->channels.size();j++){dst->channels.push_back(m_channels[src->channels[j]->index]);} }//单独循环 m_sortJoints全部new出来后执行for (int i=0;i<m_sortJoints.size();i++){BJoint* dst = m_sortJoints[i];BJoint* src = bvh.m_sortJoints[i];dst->parent = (src->parent==NULL)? NULL:m_sortJoints[src->parent->index]; for (int j=0;j<src->children.size();j++){dst->children.push_back(m_sortJoints[src->children[j]->index]);} }/*vector<BChain> */m_chains = bvh.m_chains; m_curFrame = bvh.m_curFrame ;m_frameNum = bvh.m_frameNum ; m_ifps = bvh.m_ifps ; m_motions = new float[m_frameNum * m_channelNum]; memcpy(m_motions,bvh.m_motions,4*m_frameNum * m_channelNum);CalKeyFrame();
}bool BVHSkeleton::GenTposeFromSkeleton(RendSys::Skeleton* skeleton)
{if (skeleton==NULL||skeleton->m_root==NULL){return false;}Free();#define PosChannelNum 6//要求 bip01 必须且只能挂接在 一个dummy01上m_channelNum = skeleton->m_boneNum*3 + PosChannelNum; //只有dummy01 root具有平移 其它没有活塞关节或定向关节m_frameNum = 1;m_ifps = 0.03f;//pose->m_root->m_frameLine.;m_motions = new float[m_channelNum*m_frameNum];//通道数量m_channels.resize(m_channelNum);m_sortJoints.resize(skeleton->m_boneNum);BJoint* jointRoot = new BJoint;vector<BJoint*> jointStack;jointStack.push_back(jointRoot);vector<Bone*> boneStack;boneStack.push_back(skeleton->m_root);MixSkeleton mixSkeleton;mixSkeleton.SetSkeleton(skeleton);mixSkeleton.SetCurTimeFrame(0);mixSkeleton.Advance(0,true);mat4 matInverse;while(boneStack.size()>0){BJoint* joint = jointStack.back();jointStack.pop_back();Bone* bone = boneStack.back();boneStack.pop_back();joint->isSite = (bone->subBoneNum>0)?false:true;joint->name = bone->boneName.c_str();if (StriStr(joint->name.c_str(),"footstep")){joint->bMount = true;}//pose 中已经拓扑排序joint->index = skeleton->m_id2indexs[bone->boneID];m_sortJoints[joint->index] = joint;//注意 dummy01和bip01 都有旋转和平移属性//坐标的偏移量if ( joint->index==0//dummy01||joint->index==1//bip01){joint->offset = vec3();}else{RendSys::Frame* frame = mixSkeleton.GetMixFrameFromIndex(bone->boneID-1);//bone->m_frameLine.m_keyFrames[0].m_matrix.GetTranslate()RendSys::Frame* parentFrame = mixSkeleton.GetMixFrameFromIndex(bone->parentBoneID-1);//bone->parentBone->m_frameLine.m_keyFrames[0]joint->offset = frame->m_matrix.GetTranslate() - parentFrame->m_matrix.GetTranslate();//matInverse = parentFrame->m_matrix;//matInverse.Inverse();//joint->offset = matInverse*frame->m_matrix.GetTranslate();//不一定非要轴对齐 但是要用相同的Tpose来匹配offsetjoint->TposeRot = frame->m_matrix;joint->TposeRot.SetTranslate(vec3());joint->TposeRotInv = joint->TposeRot;joint->TposeRotInv.Inverse();//Inverse4() 无影响int a = 0;}if(joint->isSite){joint->site = vec3();//?}if ( joint->index==0//dummy01||joint->index==1//bip01){//创建通道joint->channels.resize(6);Channel* channel;int offset = joint->index*6;channel = new Channel; channel->type = Xposition; channel->index = offset+0; joint->channels[0] = channel; m_channels[offset+0] = channel;channel = new Channel; channel->type = Yposition; channel->index = offset+1; joint->channels[1] = channel; m_channels[offset+1] = channel;channel = new Channel; channel->type = Zposition; channel->index = offset+2; joint->channels[2] = channel; m_channels[offset+2] = channel;channel = new Channel; channel->type = Zrotation; channel->index = offset+3; joint->channels[3] = channel; m_channels[offset+3] = channel;channel = new Channel; channel->type = Xrotation; channel->index = offset+4; joint->channels[4] = channel; m_channels[offset+4] = channel;channel = new Channel; channel->type = Yrotation; channel->index = offset+5; joint->channels[5] = channel; m_channels[offset+5] = channel;}else{//创建通道joint->channels.resize(3);Channel* channel;int offset = joint->index*3 + PosChannelNum;channel = new Channel; channel->type = Zrotation; channel->index = offset+0; joint->channels[0] = channel; m_channels[offset+0] = channel;channel = new Channel; channel->type = Xrotation; channel->index = offset+1; joint->channels[1] = channel; m_channels[offset+1] = channel;channel = new Channel; channel->type = Yrotation; channel->index = offset+2; joint->channels[2] = channel; m_channels[offset+2] = channel;}Bone* subBone = bone->subBoneHead;for (int i=0;i<bone->subBoneNum;++i){//正向添加BJoint* newJoint = new BJoint;newJoint->parent = joint;joint->children.push_back(newJoint);//入栈jointStack.push_back(newJoint);boneStack.push_back(subBone);subBone = subBone->next;//debug//newJoint->name = subBone->boneName.c_str();}}topology sorted//m_sortJoints.clear();//vector<BJoint*> jointStack;//jointStack.push_back(root);//while(jointStack.size()>0)//{// BJoint* joint = jointStack.back();// jointStack.pop_back();// joint->index = m_sortJoints.size();// m_sortJoints.push_back(joint);// //for (int i=0;i<joint->children.size();++i)// for (int i=int(joint->children.size())-1;i>=0;--i)// {// jointStack.push_back(joint->children[i]);// }//}//Tpose 没有动作 只有一个空白帧memset(m_motions,0,m_channelNum*m_frameNum*4);并不需要motions数据 以下只是让渲染体模式时位置正确//int f=0;//{// int offsetF = m_channelNum*f;// for (int i=0;i<m_sortJoints.size();++i)// {// BJoint* joint = m_sortJoints[i]; // BJoint* parentJoint = joint->parent;// int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);// int id = i+1;// if (index>=skeleton->m_boneNum)// {// break;// }// RendSys::Frame* frame = mixSkeleton.GetMixFrameFromIndex(index);// RendSys::Frame* parentFrame = NULL;// if (parentJoint)// parentFrame = mixSkeleton.GetMixFrameFromIndex(parentJoint->index);// float* motion = NULL;// if ( joint->index==0//dummy01// )// {// motion = m_motions + offsetF;// }// else if (joint->index==1//bip01// )// {// motion = m_motions + (offsetF+6);// }// if ( joint->index==0//dummy01// )// {// //dummy01有位移// motion[0] = frame->m_matrix.mat[12];// motion[1] = frame->m_matrix.mat[13];// motion[2] = frame->m_matrix.mat[14];// }// else if ( joint->index==1//bip01// )// {// //bip01有位移// motion[0] = frame->m_matrix.mat[12] - parentFrame->m_matrix.mat[12];// motion[1] = frame->m_matrix.mat[13] - parentFrame->m_matrix.mat[13];// motion[2] = frame->m_matrix.mat[14] - parentFrame->m_matrix.mat[14];// }// }//}CalKeyFrame();return true;
}bool BVHSkeleton::GenAnimFromSkeleton(RendSys::Skeleton* skeleton,BVHSkeleton* Tpose)
{if (skeleton==NULL||skeleton->m_root==NULL){return false;}Free();(*this) = *Tpose;ResizeMotion(skeleton->GetMaxTimeFrame(),m_channelNum);//要求 bip01 必须且只能挂接在 一个dummy01上//注意 dummy01和bip01 都有旋转和平移属性//bvh是逐帧捕捉动画, 同时欧拉角插值可能产生错误结果//RendSys::Skeleton是关键帧动画,可以插值,各骨骼关键帧可以不同MixSkeleton mixSkeleton;mixSkeleton.SetSkeleton(skeleton);vec3 dir;vec3 ang;mat4 matLocal;mat4 matInverse;mat4 mat;for (int f=0;f<m_frameNum;++f){mixSkeleton.SetCurTimeFrame(f);mixSkeleton.Advance(0,true);int offsetF = m_channelNum*f;root:先绕原点旋转再平移到root关键帧位置 keyMotion包含位置 offset为0子骨骼:先绕父骨骼起点旋转再平移到父骨骼的末端 keyMotion一般只含旋转 offset不为0for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i]; BJoint* parentJoint = joint->parent;int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);int id = i+1;if (index>=skeleton->m_boneNum){break;}RendSys::Frame* frame = mixSkeleton.GetMixFrameFromIndex(index);RendSys::Frame* parentFrame = NULL;if (parentJoint)parentFrame = mixSkeleton.GetMixFrameFromIndex(parentJoint->index);float* motion = NULL;if ( joint->index==0//dummy01){motion = m_motions + offsetF;}else if (joint->index==1//bip01){motion = m_motions + (offsetF+6);}else{motion = m_motions + (offsetF+i*3+PosChannelNum);}//先旋转 后offsetmatLocal = frame->m_matrix*joint->TposeRotInv; //右乘 注意乘法顺序if (parentJoint){matInverse = parentFrame->m_matrix * parentJoint->TposeRotInv; //右乘matInverse.Inverse();matLocal = matInverse*matLocal; //左乘}//matLocal.SetTranslate(vec3());//参见CalKeyFrame 注意旋转顺序 ang = matLocal.GetEulerAngRadYXZ(); //no scaleif ( joint->index==0//dummy01){//dummy01有位移motion[0] = frame->m_matrix.mat[12];motion[1] = frame->m_matrix.mat[13];motion[2] = frame->m_matrix.mat[14];motion[3] = ang.z;motion[4] = ang.x;motion[5] = ang.y;}else if ( joint->index==1//bip01){//bip01有位移motion[0] = frame->m_matrix.mat[12] - parentFrame->m_matrix.mat[12];motion[1] = frame->m_matrix.mat[13] - parentFrame->m_matrix.mat[13];motion[2] = frame->m_matrix.mat[14] - parentFrame->m_matrix.mat[14];motion[3] = ang.z;motion[4] = ang.x;motion[5] = ang.y;}else{motion[0] = ang.z;motion[1] = ang.x;motion[2] = ang.y;}//mat4 check;//if (parentJoint)//{// dir = joint->offset;// dir.Normalize();// mat.FromToDir(vec3(1,0,0),dir);// check = joint->parent->matFinal * mat;//}//else//{// check = mat;//}//if (check!=joint->matFinal)//{// int a = 0;//}}}CalKeyFrame();return true;
}void BVHSkeleton::CopyToSkeleton(RendSys::Skeleton* skeleton)
{int JointSize = m_sortJoints.size();if (JointSize==0 || m_frameNum==0){return;}//resize 逐帧动画 有可能目前关键帧数为0for (int i=0;i<JointSize;++i){int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);int id = i+1;if (index>=skeleton->m_boneNum){break;}//一次计算后缓存在内存RendSys::Bone* bone = skeleton->GetBone(id);if (bone == NULL)continue;bone->m_frameLine.ResizeKeyFrame(m_frameNum);}for(int f=0;f<m_frameNum;f++){m_curFrame = f;Forward();for (int i=0;i<JointSize;++i){BJoint* joint = m_sortJoints[i];int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);int id = i+1;if (index>=skeleton->m_boneNum){break;}//逐帧动画RendSys::Bone* bone = skeleton->GetBone(id);if (bone == NULL)continue;RendSys::Frame* frame = bone->m_frameLine.GetKeyFrameFromIndex(f);if (frame) {frame->m_timeFrame = f;frame->m_matrix = joint->matFinal;frame->CalFromMatrix();}}}
}void BVHSkeleton::CopyToSkeleton2(RendSys::Skeleton* skeleton)
{int JointSize = m_sortJoints.size();if (JointSize==0 || m_frameNum==0){return;}//需要事先K好帧for(int f=0;f<m_frameNum;f++){m_curFrame = f;Forward();for (int i=0;i<JointSize;++i){BJoint* joint = m_sortJoints[i];int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);int id = i+1;if (index>=skeleton->m_boneNum){break;}//逐帧判断关键帧RendSys::Bone* bone = skeleton->GetBone(id);if (bone == NULL)continue;RendSys::Frame* frame = bone->m_frameLine.GetKeyFrameFromTime(f);if (frame) {frame->m_timeFrame = f;frame->m_matrix = joint->matFinal;frame->CalFromMatrix();}}}
}void BVHSkeleton::CopyToMixSkeleton(RendSys::MixSkeleton* mixSkeleton,float weight/*=1*/)
{int JointSize = m_sortJoints.size();if (JointSize==0 || m_frameNum==0){return ;}for (int i=0;i<JointSize;++i){BJoint* bone = m_sortJoints[i];int index = i;//mixSkeleton->m_skeleton->GetBoneIndex(bone->boneID);if (index>=mixSkeleton->m_skeleton->m_boneNum){break;}RendSys::Frame* frame = &mixSkeleton->m_mixFrameArr[index];//GetMixFrameFromIDframe->m_matrix = bone->matFinal;frame->CalFromMatrix();}
}void BVHSkeleton::AnimRetargetingFrom(BVHSkeleton& srcBVH,float scale)
{if (m_channelNum!=srcBVH.m_channelNum){return;}//重定向this->ResizeMotion(srcBVH.GetFrameNum(), srcBVH.GetChannelNum());for(int frame = 0; frame != srcBVH.GetFrameNum(); ++frame){//? 通道数量不一致??for(int c = 0; c != m_channelNum; ++c){switch(m_channels[c]->type){case(Xposition):case(Yposition):case(Zposition)://骨架缩小scale倍, root骨骼位移也要缩小scale倍this->SetMotion(frame, c, srcBVH.GetMotion(frame, c) * scale); //具有位移时才需要重新计算CalKeyFrame 一般只有root节点带位移break;default:this->SetMotion(frame, c, srcBVH.GetMotion(frame, c));break;}}}CalKeyFrame();
}void BVHSkeleton::AnimRetargetingWith(BVHSkeleton& dstTpose,float scale/*=1*/)
{if (m_channelNum!=dstTpose.m_channelNum){return;}for (int i=0;i<m_sortJoints.size();i++){BJoint* dst = m_sortJoints[i];BJoint* src = dstTpose.m_sortJoints[i];//dst->name = src->name ;//dst->index = src->index ;dst->offset = src->offset ;dst->isSite = src->isSite ;dst->site = src->site ;dst->keyMotion = src->keyMotion ;dst->matFinal = src->matFinal ;dst->TposeRot = src->TposeRot ;dst->TposeRotInv = src->TposeRotInv ;}CalKeyFrame();
}int BVHSkeleton::GuessJoint(const char* name)
{//!没有做id重排,所以注意导出动作时要保证骨骼顺序一致// ●Bip01 HeadNub// |// ●Bip01 Head// |// |Neck // L Clavicle●---●---●R Clavicle// ╱ | ╲// ╱ | ╲// L UpperArm● | ●R UpperArm// ╱ | ╲// ╱ ●Spine1 ╲// L Forearm● | ●R Forearm// ╱ | ╲// L Hand ● | ●R Hand// L... ╱|╲ ●Spine ╱|╲ R Finger0、1、2// L... | | | ╱|╲ | | | R Finger01、11、21// L... ☉☉☉ ╱ | ╲ ☉☉☉R Finger0Nub、1Nub、2Nub// ╱ ●Pelvis // ╱ ●Bip01 ╲// L Thigh● ╱ . ●R Thigh// ╱ ╱ . ╲// ╱ ●Dummy01 ╲// ╱ . ╲// L Calf ● . ●R Calf// ╱ . ╲// L Foot● ●Footsteps ●R Foot// L Toe0| |R Toe0// L Toe0Nub☉ ☉R Toe0Nub//// ●head// |// ●neck // leftCollar● | ●rightCollar// ╱╲ | ╱╲// ╱ ╲| ╱ ╲// leftUpArm● ●chest ●rightUpArm// ╱ | ╲// ╱ | ╲// leftLowAnn● | ●rightLowArm// ╱ | ╲// leftHand● | ●rightHand// ●hips// ╱╲// ╱ ╲// leftUpLeg● ●rightUpLeg// ╱ ╲// ╱ ╲// leftLowLeg● ●rightLowLeg// ╱ ╲// ╱ ╲// leftFoot● ●rightFoot//忽略Bip01 和Bip02的区别for (int i=0;i<m_sortJoints.size();++i){if (m_sortJoints[i]->name==name){return i;}}return 0;
}void BVHSkeleton::CalKeyFrame(int frame)
{if (m_sortJoints.size()==0 || m_frameNum==0){return;}mat4 matOffset;mat4 trans;mat3 rotY;mat3 rotX;mat3 rotZ;float sign = 1;//-1 左手坐标系mat4 mat;vec3 transPos;int begin = 0;int end = m_frameNum-1;if (frame != -1){begin = frame;end = frame;}for (int f=begin;f<=end;++f){float* mov = &m_motions[f*m_channelNum];for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];if (frame == -1){joint->keyMotion.resize(m_frameNum);}transPos.x = 0;transPos.y = 0;transPos.z = 0;int ChannelNum = joint->channels.size();for (int c=0;c<ChannelNum;++c){float value = *mov++;switch(joint->channels[c]->type){case(Xposition):transPos.x = value;break;case(Yposition):transPos.y = value;break;case(Zposition):transPos.z = value;break;case(Zrotation):rotZ.FromRotateZ(sign * value);break;case(Yrotation):rotY.FromRotateY(sign * value);break;case(Xrotation):rotX.FromRotateX(sign * value);break;}}trans.SetTranslate(transPos);matOffset.SetTranslate(joint->offset);//骨骼动画使用的是欧拉角旋转//renderdriver 默认的是固定角旋转//joint->keyMotion[f] = matOffset * trans * (rotY * rotX * rotZ);// 固定角旋转和欧拉角旋转矩阵相乘顺序正好相反joint->keyMotion[f] = matOffset * trans * (rotZ * rotX * rotY); // 先旋转后平移}}
}void BVHSkeleton::ScaleJoints(float scale)
{if (m_sortJoints.size()==0 || m_frameNum==0){return ;}for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];joint->offset *= scale;}for (int f=0;f<m_frameNum;++f){float* mov = &m_motions[f*m_channelNum];for (int i=0;i<m_sortJoints.size();++i){BJoint* joint = m_sortJoints[i];int ChannelNum = joint->channels.size();for (int c=0;c<ChannelNum;++c){switch(joint->channels[c]->type){case(Xposition):case(Yposition):case(Zposition):(*mov) *= scale; //具有位移时才需要重新计算CalKeyFramebreak;}mov++;}}} CalKeyFrame();
}void BVHSkeleton::ResizeMotion(int frameNum, int channelNum)
{if (frameNum<=0 || channelNum<=0){return;}m_frameNum = frameNum;SafeDeleteArray(m_motions);m_motions = new float[frameNum*channelNum];memset(m_motions,0,frameNum*channelNum*4);
}
void BVHSkeleton::SetMotion(int frame, int c, float value)
{if (frame >= m_frameNum){return;}if (c >= m_channelNum){return;}m_motions[frame*m_channelNum + c] = value;
}float BVHSkeleton::GetMotion(int frame, int c) const
{if (frame >= m_frameNum){return 0;}if (c >= m_channelNum){return 0;}return m_motions[frame*m_channelNum + c];
}
float BVHSkeleton::GetFps() const
{return m_ifps;
}int BVHSkeleton::GetFrameNum() const
{return m_frameNum;
}int BVHSkeleton::GetChannelNum() const
{return m_channels.size();
}int BVHSkeleton::GetJointNum() const
{return m_sortJoints.size();
}
int BVHSkeleton::GetChainNum() const
{return m_chains.size();
}
BJoint* BVHSkeleton::GetJoint(const char* name) const
{for(int i = 1; i != m_sortJoints.size(); ++i){if((m_sortJoints[i]->name) == name){return m_sortJoints[i];}}return NULL;
}
BJoint* BVHSkeleton::GetJoint(int index) const
{if (index >= m_sortJoints.size()){return NULL;}return m_sortJoints[index];
}BChain& BVHSkeleton::GetChain(int index)
{if (index >= m_chains.size()){return m_chains[0];}return m_chains[index];
}//==================^_^==================^_^==================^_^==================^_^SkinFit::SkinFit()
:m_boneFits(NULL)
{}SkinFit::~SkinFit()
{Free();
}void SkinFit::Free()
{SafeDeleteArray(m_boneFits);
}bool SkinFit::LoadFromFile(const char* filename)
{Free();File file;if (file.Fopen(filename,"rt")){if(strcmp("SkinFit1.0",file.ReadString())!=0)return false;m_boneNum = file.ReadInt();m_boneFits = new BoneFit[m_boneNum];vec3 rot;for (int i=0;i<m_boneNum;i++){m_boneFits[i].boneID = file.ReadInt();file.ReadFloatArray(&m_boneFits[i].skinScale.x,3);file.ReadFloatArray(&m_boneFits[i].boneScale.x,3);file.ReadFloatArray(&rot.x,3);m_boneFits[i].rot.FromEulerAngRadXZY(rot);}}return true;
}void SkinFit::SaveToFile(const char* filename)
{File file;if (file.Fopen(filename,"wt")){file.WriteString("SkinFit1.0"); file.Fprintf("\n");file.WriteInt(m_boneNum); file.Fprintf(" ");vec3 rot;for (int i=0;i<m_boneNum;i++) {file.WriteInt(m_boneFits[i].boneID); file.Fprintf(" [");file.WriteFloatArray(&m_boneFits[i].skinScale.x,3); file.Fprintf("] [");file.WriteFloatArray(&m_boneFits[i].boneScale.x,3); file.Fprintf("] [");rot = m_boneFits[i].rot.GetEulerAngRadXZY();file.WriteFloatArray(&rot.x,3); file.Fprintf("]\n");}}
}bool SkinFit::Init(BVHSkeleton* tpose)
{Free();m_boneNum = tpose->GetJointNum();m_boneFits = new BoneFit[m_boneNum];for (int i=0;i<m_boneNum;i++){m_boneFits[i].boneID = i+1;//tpose->GetJoint(i).index+1;}Reset();return true;
}void SkinFit::Reset()
{vec3 identity (1,1,1);for (int i=0;i<m_boneNum;i++){m_boneFits[i].skinScale = identity;m_boneFits[i].boneScale = identity;m_boneFits[i].rot.Identity();// = vec3();}
}void SkinFit::Add(SkinFit& delta)
{if(m_boneNum != delta.m_boneNum)return;for (int i=0;i<m_boneNum;i++){m_boneFits[i].skinScale *= delta.m_boneFits[i].skinScale;m_boneFits[i].rot *= delta.m_boneFits[i].rot;m_boneFits[i].boneScale *= delta.m_boneFits[i].boneScale;}
}void SkinFit::FitSkin(RendSys::Mesh* mesh)
{if (mesh==NULL||mesh->m_vertexWeights==NULL){return;}BoneFit* fits[128];//id2indexmemset(fits,0,128*4);for (int i=0;i<m_boneNum;i++) {int boneID = m_boneFits[i].boneID;fits[boneID] = &m_boneFits[i];}for (int i=0;i<mesh->m_vVertexNum;i++) {RendSys::Mesh::VertexWeight* weight = &mesh->m_vertexWeights[i];for (int w=0;w<weight->boneNum;w++) {RendSys::Mesh::BoneWeight* bw = &weight->boneWeight[w];BoneFit* fit = fits[bw->boneId];if (fit){//可能骨骼不匹配 避免crash//if (abs(fit->scale.LengthSq()-1) > _EPSILON)//{//}bw->x *= fit->skinScale.x;bw->y *= fit->skinScale.y;bw->z *= fit->skinScale.z;}}}
}void SkinFit::FitSkin(RendSys::MovieClip* movie)
{MovieThrough thtorgh(movie);RendSys::MovieClip* subMovie = thtorgh.GetFirst();while (subMovie){RendSys::MC_Skeleton* skeletonMovie = dynamic_cast<MC_Skeleton*>(subMovie);if (skeletonMovie){RendSys::Mesh* mesh = skeletonMovie->GetMesh();FitSkin(mesh);}FitSkin(subMovie);subMovie = thtorgh.GetNext();}
}void SkinFit::FitTpose(BVHSkeleton* tpose)
{if (tpose==NULL){return;}BoneFit* fits[128];//id2indexmemset(fits,0,128*4);for (int i=0;i<m_boneNum;i++) {int boneID = m_boneFits[i].boneID;fits[boneID] = &m_boneFits[i];}for (int i=0;i<tpose->GetJointNum();i++) {BJoint* joint = tpose->GetJoint(i);int boneID = i+1;BoneFit* fit = fits[boneID];if (fit){//可能骨骼不匹配 避免crash//if (abs(fit->scale.z-1) > _EPSILON)//{//}//joint->offset *= fit->scale.z; //自身X缩放:自身offset发生变化? 沿着自身纵轴,应该是缩放子骨骼更好些? 有些具有多个子骨骼?//joint->offset *= fit->skinScale.x;joint->offset *= fit->boneScale.x;//自身旋转: 自身rot发生变,化子骨骼的offset发生变化,子骨骼旋转不变for(int j=0;j<joint->children.size();j++){BJoint* child = joint->children[j];child->offset = fit->rot*child->offset;}joint->TposeRot = fit->rot * joint->TposeRot; //旋转不够准确 ?//joint->TposeRot = joint->TposeRot * fit->rot;joint->TposeRotInv = joint->TposeRot;joint->TposeRotInv.Inverse();}}tpose->CalKeyFrame();tpose->Forward();
}//==================^_^==================^_^==================^_^==================^_^static BVHSkeleton* m_BVH;
void TestInitBVH()
{{//man_small_Tpose 是 man_big_dongzuo 骨架的0.5倍大小BVHSkeleton tpose;//tpose.LoadFromFile("data/test/kinematics/man_small_Tpose.bvh");//tpose.LoadFromFile("data/test/kinematics/man_small_Tpose_rightarm.bvh");//保留右肩上提 右臂过长的体型特点tpose.LoadFromFile("data/test/kinematics/man_small_Tpose_humpback.bvh");//保留驼背的体型特点//man_small_Tpose2缩小到0.1倍大小tpose.ScaleJoints(0.2f);tpose.SaveToFile("data/test/kinematics/man_small_Tpose2.bvh");}{BVHSkeleton srcBVH;srcBVH.LoadFromFile("data/test/kinematics/man_big_dongzuo.bvh");BVHSkeleton res;res.LoadFromFile("data/test/kinematics/man_small_Tpose2.bvh");//man_small_Tpose2已经缩小到0.1倍大小res.AnimRetargetingFrom(srcBVH,0.1f);res.SaveToFile("data/test/kinematics/man_small_dongzuoresult.bvh");}m_BVH = new BVHSkeleton;m_BVH->LoadFromFile("data/test/kinematics/man_small_dongzuoresult.bvh");//m_BVH->LoadFromFile("data/test/kinematics/test01.bvh");{//预生成tpose//Skeleton commonTposeSk; commonTposeSk.LoadFromFile("data/test/kinematics/commonman_tpose.bone");//BVHSkeleton commonTposeBVH;//commonTposeBVH.GenTposeFromSkeleton(&commonTposeSk);//commonTposeBVH.SaveToFile("data/test/kinematics/commonman_tpose.bvh");//预生成bvh anim//Skeleton commonAnimSk; commonAnimSk.LoadFromFile("data/test/kinematics/commonman_run.bone");//BVHSkeleton commonTposeBVH; commonTposeBVH.LoadFromFile("data/test/kinematics/commonman_tpose.bvh");//BVHSkeleton commonAnimBVH;//commonAnimBVH.GenAnimFromSkeleton(&commonAnimSk,&commonTposeBVH);//commonAnimBVH.SaveToFile("data/test/kinematics/commonman_run.bvh");}{实时重定向//BVHSkeleton commonTposeBVH; commonTposeBVH.LoadFromFile("data/test/kinematics/commonman_tpose.bvh");//BVHSkeleton coolTposeBVH; commonTposeBVH.LoadFromFile("data/test/kinematics/coolman_tpose.bvh");//BVHSkeleton commonAnimBVH; commonAnimBVH.LoadFromFile("data/test/kinematics/commonman_run.bvh");//BVHSkeleton coolAnimBVH;//coolAnimBVH = commonAnimBVH;//coolAnimBVH.AnimRetargetingWith(coolTposeBVH);//coolAnimBVH = coolTposeBVH;//coolAnimBVH.AnimRetargetingFrom(coolTposeBVH);//Skeleton coolAnimSK;//coolAnimBVH.CopyToSkeleton(&coolAnimSK);太多 直接使用不预先保存coolAnimSK.SaveToFile("data/test/kinematics/coolman_result.bone");}
}void TestRendBVH()
{m_BVH->Update();m_BVH->Render();}
//========================================================
// @Date: 2016.05
// @File: Include/Render/FABRIKSkeleton.h
// @Brief: 逆向运动学
// @Author: LouLei
// @Email: twopointfive@163.com
// @Copyright (Crapell) - All Rights Reserved
//========================================================#pragma once
//#ifndef __ReproductMesh__H__
//#define __ReproductMesh__H__#include "Math/Mathlib.h"
#include <vector>//正向动力学(FK)和反向动力学(IK Inverse kinematics)。
//反向动力学中比较流行的方法则是 Cyclic Coordinate Descent(CCD)和 Forward And Backward Reaching Inverse Kinematics (FABRIK)。
//几何分析与雅可比矩阵的方法,计算起来比较复杂,用的不是太多。//目标可以到达的范围: 最远为各臂长之和,最短为各臂长排列正负号之和的最小绝对值//CCD的具体实现:
//1从骨骼链上最深的子骨节开始相对于(父坐标系)原点进行旋转,使它指向效应点。
//2将这个骨节的父节点针对于(祖坐标系)原点进行旋转,使父骨节的原点到新旋转的子骨节端点的连线指向效应点。
//3对每个骨节进行1~2步骤的处理。
//上述步骤进行多次循环从而得到更加稳定的值。//FABRIK:不需要计算旋转,只需要计算线性位置,快速并且变化光滑,不会存在断点或者突变。FABRIK的详细理论可以参看Andreas Aristidou的论文,从收敛等各个角度详细阐述了FABRIK的特性。
//给定四个点,假定原点P1限制不能移动,移动P2、P3、P4使得最终P4到达目标点
//1forward 将P4移动到目标点P4',然后连接目标点和P3,在这条线上取距离为d3得到P3', 重复这个过程直到得到P1',P1'偏移了固定点。
//2backward 将P1移回到固定点,然后连接固定点和P2,在这条线上取距离为d2得到P2',重复这个过程直到得到P4',P4'偏移了目标点,但是比第一步前更近了
//3迭代到 足够近
//分叉骨骼链:比如手掌
//第一步还是forward 只不过这个时候不是到达start起点,而是从每一个end effector到达距离他最近的sub-base节点。对于有多个end effector的情况,这样做会得到多个sub-base的新位置,我们取所有sub-base新位置的中心点作为最终的sub-base的新位置。
//如果这个sub-base又连接到另外一个sub-base上面了,那么我们重复上面的过程。如果sub-base连接到了起点上面,我们实际上就最终到达了起始点。
//第二部还是backward,这个时候我们从起点出发,然后遇到sub-base的时候我们就把每个支路作为单独的网络分别求解,过程和上面没有任何不同!//FABRIK 注视动画(眼睛前方添加骨骼bonenub bonenub目标点在头部单位圆上转动 )using namespace std;//目标点
class EndEffector
{
public:EndEffector();void Render();
public:vec3 pos;mat4 matFinal;
};//一个joint对应一个matrix, bone(link)比joint少一个
class IKBone
{
public:IKBone();void Render();//newStartPos 子骨骼的起点 即父骨骼的终点 多个子骨骼起点必须相同??IKBone* AddBone(const vec3& pos,const char* name="",int boneID=-1,const mat4* mat=NULL);void Set(const vec3& startTpose,const vec3& endTpose,const char* name="",int boneID=-1,const mat4* matTpose=NULL);public:char name[64];int boneID; vec3 startPos;vec3 endPos;EndEffector endEffector;float length; //骨骼长度 末端骨骼无长度float pistonLen; //可伸缩活塞长度 reaching时可以拉长活塞mat4 matFinal;mat4 matLocal;bool tposeMatSetted;mat4 matTpose;vec3 startTpose;vec3 endTpose;IKBone* parent;vector<IKBone*> children;int backCount;//逆向计数//关节约束enum ConstraintType{None ,Fixed ,//固定 0自由度 比如锁骨Cone ,//锥角FourDir ,//四方向度数Hinge ,//绕轴旋转,限制了另外两个角的自由度Slider ,};ConstraintType constraintType;float constraintCone; //锥角vec4 constraintFourDir;//限制四方向度数float constraintTwist; //限制扭曲
};namespace RendSys
{class Skeleton;class MixSkeleton;
}//一根骨骼链只能在末端添加子骨骼,在链的中间A处添加子骨骼C是不允许的,
//变通的方法:给A的父骨骼额外添加一个分叉子骨骼B(B是A的兄弟 让B与A始终重合),再在新的分叉末端B添加C
class IKSkeleton
{
public:IKSkeleton();~IKSkeleton();void Free();bool GenFromSkeleton(RendSys::Skeleton* pose);bool SetRoot(IKBone * root);void Solve();void Render();//根据末端骨骼名获取所在链、EndEffectorIKBone* GetBone(const char* name);void CopyToSkeleton (RendSys::Skeleton* anim,int keyFrame);void CopyToMixSkeleton(RendSys::MixSkeleton* anim,float weight=1);void SortBones();void CalBoneMatrix();//关节约束void ConstraintCone(vec3& point,IKBone* parent,IKBone* bone);void ConstraintFourDir(vec3& point,IKBone* parent,IKBone* bone);void ConstraintHinge(vec3& point,IKBone* parent,IKBone* bone);void Backward(); void Forward(); private:public:vec3 m_origin; //固定点或起始点IKBone* m_root; //m_sortJoints[0]vector<IKBone*> m_sortJoints; //拓扑排序};
游戏动画引擎 -(Crapell Game Engine Design - animation)相关推荐
- 【3D服装级设计/游戏动画引擎】Marvelous Designer软件
MD可以满足设计师对服装细节和品质的苛求,并且和OBJ文件以及其它3D软件也可以很好的兼容,所以MD不仅是各大服装设计师的必备软件,更是EA,Konami等全球顶尖的游戏工作室的最爱. 附 Blend ...
- Delphi程序员的“Unity3D”:开源跨平台游戏开发引擎Castle Game Engine(CGE)
官方网站:https://castle-engine.io/features.php Delphi牛人开发的3D/2D开源游戏引擎支持: 跨平台(Linux,Windows,Mac OS X,Free ...
- 【转】游戏引擎剖析(Game Engine Anatomy 101)
游戏引擎剖析(Game Engine Anatomy 101) 原文作者:Jake Simpson 译者: 向海 Email:GameWorldChina@myway.com 英文原版下载 : 第1部 ...
- [转载]游戏相关引擎荟萃2
[转载]游戏相关引擎荟萃2 Name Language Platform License Graphics Sound Networking Scripting Other features Plus ...
- 如何将unity3d动画嵌入html,在Unity3D中使用精灵动画引擎制作动画的两种方法
7月28日消息,如今的游戏玩家对于游戏角色的动作要求越来越高,给开发者提出了众多的要求,工作量也相应上升.那么如何才能简单快速地制作角色动画以提升效率呢?下面就和大家分享两个在Unity3D中使用精灵 ...
- 如何学好游戏3D引擎编程(摘抄)
网上看到,觉得很好,因此收藏一下,侵删. <如何学好游戏3D引擎编程> 此篇文章献给那些为了游戏编程不怕困难的热血青年,它的神秘要我永远不间断的去挑战自我,超越自我,这样才能攀登到游戏技术 ...
- 常见3D游戏物理引擎总结
1. Havok: 老牌的君王,支持功能如下: http://www.havok.com · Collision Detection - including Continuous P ...
- Unity游戏动画 从入门到住院 1
http://www.gameres.com/674582.html Unity3D游戏美术全攻略:从入门到精通 发布者: 小篱 | 发布时间: 2016-8-8 13:40| 评论数: 1 文/拉撒 ...
- unity游戏动画 从入门到住院 二 模型导入
第一篇:unity游戏动画 从入门到住院 一 导出设置 好的,现在我们已经导出了一个干净或者不干净的FBX,总之他是可用的.至于导出如何设置请看前文链接--既然导出来了我们如何在unity中使用它呢? ...
最新文章
- iOS开发8:使用Tool Bar切换视图
- 谷歌时间晶体登上Nature,诺奖得主重大猜想成为现实
- 八周二次课(5月14日)
- 嵌入式Linux系统编程学习之二十四消息队列
- 吃了核辐射食物怎么办_我们经常吃的猪肉,相当一部分是核辐射照过的,会对身体有害吗...
- RadComboBox的用法
- ContextLoaderListener和Spring MVC中的DispatcherServlet加载内容的区别
- 文荣:7月24日阿里云上海峰会网络大神
- laravel 中Predis使用手册
- 打印机质量测试软件,打印机断针测试软件
- php测试宽带速度慢,别被运营商骗了! 手把手教你学会测自家网速
- 7月,带你阅读图灵原创图书以及上榜新书
- 看网易的lofter,预测轻博客的未来
- 科技部等6部门发文,推动AI场景创新;『精益副业』教程序员优雅做副业;『可扩展系统』设计全教程;人物动作数据集;前沿论文 | ShowMeAI资讯日报
- 输入框添加Emoje表情demo
- nyoj 239 月老的难题 【二分匹配之匈牙利】
- 忽悠自由主义_所有教育工作者都应该知道的16种自由主义
- 长沙市21中2021年高考成绩查询,长沙几大高中名校2020年高考成绩放榜了,这样的成绩你满意吗...
- python使用pika操作rabbitmq总结(一)
- No module named _lzma