
顔がないとちょっとホラーになってしまうので、とりあえずって感じで乗っけました。頂点数350ぐらい。超ローポリですな。
で、歩きのアクションを作成中です。
うーむ、こりゃ色気も何もないな。まいっか、今回の目的はそれじゃないし。
ゲームを作りたくてOpenGLや関連技術を勉強中です。ちなみに仕事は、同じIT系とはいえ業務系ソフト開発をかれこれ20年以上やってます。ゲーム開発...むずい...
void Model::draw() const {
for (unsigned int faceIdx = 0; faceIdx < numFaces; faceIdx++) {
Face *f = &faces[faceIdx];
bool useTexture;
int lastImgIdx = -1;
// テクスチャの準備
:
// 描画
glBegin(GL_QUADS);
std::vector<unsigned int>::iterator itv;
for (unsigned int i=0; i<f->numVerts; i++) {
FaceVertex* fv = &f->verts[i];
if (useTexture) glTexCoord2f(fv->u, fv->v);
Vertex *v = &vertices [fv->idx];
if (curAction) {
// アニメーション中
glVertex3f(v->anim.x, v->anim.y, v->anim.z);
} else {
// 静止中
glVertex3f(v->rest.x, v->rest.y, v->rest.z);
}
}
glEnd();
}
}
void Matrix4x3::fromQuaternion(const Quaternion &q) {
:
m11 = 1.0f - yy*q.y - zz*q.z;
m12 = xx*q.y + ww*q.z;
m13 = xx*q.z - ww*q.x;
m21 = xx*q.y - ww*q.z;
m22 = 1.0f - xx*q.x - zz*q.z;
m23 = yy*q.z + ww*q.x;
m31 = xx*q.z + ww*q.y;
m32 = yy*q.z - ww*q.x;
m33 = 1.0f - xx*q.x - yy*q.y;
:
}
m13 = xx*q.z - ww*q.x;
m13 = xx*q.z - ww*q.y;
bool Model::startAction(std::string action) {
const Action *a = findAction(action);
if (a == NULL) return false;
curAction = (Action *)a;
timer.start();
return true;
}
void Model::step() {
static float fps = frames_per_sec / 1000.0f;
if (curAction != NULL) {
curAction->step((unsigned int)(timer.getTicks() * fps));
for (unsigned int idx = 0; idx < numVerts; idx++) {
Vertex *v = &vertices[idx];
if (v->boneWeights.size() > 0) {
Vector3 vec(0.0, 0.0, 0.0);
std::vector<BoneWeight *>::iterator itw;
// 頂点ブレンディング
for (itw = v->boneWeights.begin(); itw != v->boneWeights.end(); ++itw) {
BoneWeight *bw = *itw;
Vector3 tmp;
if (bw->bone->armature->isSingular) {
tmp = v->rest * bw->bone->am;
} else {
tmp = v->rest * bw->bone->armature->im * bw->bone->am * bw->bone->armature->m;
}
vec += bw->weight * tmp;
}
v->anim = vec;
} else {
v->anim = v->rest;
}
}
}
}
void Model::Action::step(unsigned int frameNum) {
Matrix4x3 mat;
if (lastFrame == 0) return;
unsigned int fnum = frameNum % lastFrame;
Keyframe *prevFrame = NULL;
Keyframe *nextFrame = NULL;
std::vector<Keyframe *>::iterator itf;
// 前後のフレームを探す
for (itf = frames.begin(); itf != frames.end(); itf++) {
Keyframe *frame = *itf;
prevFrame = nextFrame;
nextFrame = frame;
if (fnum < nextFrame->num) {
break;
}
}
std::vector<ActionKey *>::iterator itk;
// 前のキーフレーム
if (prevFrame != NULL) {
for (itk = prevFrame->keys.begin(); itk != prevFrame->keys.end(); ++itk) {
ActionKey *key = *itk;
Bone* bone = key->bone;
bone->curFrameNum = frameNum;
bone->prevFrameNum = prevFrame->num;
bone->prevLoc = &key->loc;
bone->prevQ = &key->quat;
}
}
// 次のキーフレーム
for (itk = nextFrame->keys.begin(); itk != nextFrame->keys.end(); ++itk) {
ActionKey *key = *itk;
Bone* bone = key->bone;
// 線形補間
if (prevFrame != NULL && bone->curFrameNum == frameNum) {
float t = (float)(fnum - bone->prevFrameNum) / (float)(nextFrame->num - bone->prevFrameNum);
Quaternion q = slerp(*bone->prevQ, key->quat, t);
mat.fromQuaternion(q);
Vector3 loc = (*bone->prevLoc - key->loc) * t;
mat.setTranslation(loc);
} else {
mat.fromQuaternion(key->quat);
}
bone->setupMatrix(mat);
}
}
void Bone::setupMatrix(Matrix4x3& mat) {
if (isSingular) {
am = mat;
} else {
am = im * mat * m;
}
if (parent) am = am * parent->am;
}
Model::Action* Model::readActionLine(std::istream& in) {
std::string name;
in >> name;
Action *a = new Action(name);
actions.push_back(a);
return a;
}
void Model::readKeyframeLine(std::istream& in, Action& action) {
unsigned int num;
std::string boneName;
float x, y, z;
float qw, qx, qy, qz;
if (armature == NULL) {
std::cerr << "found keyframe line without previous armature line" << std::endl;
return;
}
in >> num;
Keyframe *f = new Keyframe(num);
do {
in >> boneName >> x >> y >> z >> qw >> qx >> qy >> qz;
if (in.fail()) {
break;
} else {
Bone *bone = armature->findBone(boneName);
if (bone != NULL) {
ActionKey *key = new ActionKey();
key->bone = bone;
key->loc.x = x;
key->loc.y = y;
key->loc.z = z;
key->quat.w = qw;
key->quat.x = qx;
key->quat.y = qy;
key->quat.z = qz;
f->addKey(key);
} else {
std::cerr << "not found bone named " << boneName << std::endl;
}
}
} while (true);
action.addKeyframe(f);
}
void MyModel::readWeightLine(std::istream& in, Bone& bone) {
unsigned int idx;
float weight;
do {
in >> idx >> weight;
if (in.fail()) {
break;
} else {
if (idx < numVerts) {
Vertex* v = &vertices[idx];
v->addBoneWeight(&bone, weight);
} else {
std::cerr << "vertex idx is out out range for weight. idx=" << idx << std::endl;
}
}
} while (true);
}
void Model::readArmatureLine(std::istream& in) {
std::string name;
in >> name;
if (in.fail()) {
std::cerr << "failed in reading armature line" << std::endl;
return;
}
armature = new Armature(name);
Armature *a = armature;
in >> a->m.m11 >> a->m.m12 >> a->m.m13
>> a->m.m21 >> a->m.m22 >> a->m.m23
>> a->m.m31 >> a->m.m32 >> a->m.m33
>> a->m.tx >> a->m.ty >> a->m.tz;
if (determinant(a->m) > 0.00001f) {
a->isSingular = false;
a->im = inverse(a->>m);
} else {
a->isSingular = true;
a->im.identity();
}
}
Model::Bone* Model::readBoneLine(std::istream& in) {
static Bone *lastBone = NULL;
unsigned int level;
if (armature == NULL) {
std::cerr << "found bone line without previous armature line" << std::endl;
return NULL;
}
std::string name;
in >> level >> name;
Bone *b = new Bone(name);
in >> b->m.m11 >> b->m.m12 >> b->m.m13
>> b->m.m21 >> b->m.m22 >> b->m.m23
>> b->m.m31 >> b->m.m32 >> b->m.m33
>> b->m.tx >> b->m.ty >> b->m.tz;
if (determinant(b->m) > 0.00001f) {
b->isSingular = false;
b->im = inverse(b->m);
} else {
b->isSingular = true;
b->im.identity();
}
armature->addBone(lastBone, level, b);
lastBone = b;
return b;
}
void addBone(Bone *relBone, unsigned int level, Bone *bone) {
if (level == 0) {
if (root != NULL) {
delete root;
}
root = bone;
root->armature = this;
root->parent = NULL;
} else {
if (root == NULL) {
std::cerr << "no root bone set";
return;
}
assert(relBone != NULL);
Bone *parent = relBone->findAncestor(level-1);
if (parent != NULL) {
bone->armature = this;
parent->addChild(bone);
} else {
std::cerr << "not found parent of level=" << level-1;
}
}
}
Bone* findAncestor(unsigned int level) const {
unsigned int curLevel = getLevel();
const Bone *bone = this;
while (bone != NULL && level != curLevel) {
bone = bone->parent;
--curLevel;
}
return (Bone *)bone;
}
Aの変換 x 0.5 + Bの変換 x 0.5というふうに、どのボーンからどれだけ影響を受けるかという重みを各頂点に設定することになります。また0.5ずつじゃなく、
Aの変換 x 0.7 + Bの変換 x 0.3などとすることもできます。各重みの合計が1.0(=100%)になるようにする必要があります。また、A、B、Cの3つのボーンの影響を受ける頂点は、
Aの変換 x 0.6 + Bの変換 x 0.3 + Cの変換 x 0.1などとします。影響を受けるボーンが4つ以上でも同様です。重みを加味してやると上の画像の右側のような曲がり方になります。曲がり方は各ボーンへの重みを変えることで調整することができます。
W 頂点番号1 重み1 頂点番号2 重み2 頂点番号3 重み3
def writeBone(out, mesh, bone, level):
:
grp = mesh.getVertsFromGroup(bone.name, True)
if len(grp) > 0:
out.write("W")
for i in grp:
out.write(" %d %f" % (i[0], i[1]))
out.write("\n")
if bone.hasChildren():
for b in bone.children:
writeBone(out, mesh, b, level+1)
V x Am(-1) x Fm(-1) x Fr x Fm x Um(-1) x Ur x Um x AmのFrやUrの部分です。
A アクション名
K フレーム番号 ボーン名1 平行移動1(x座標 y座標 z座標) クォータニオン1(w x y z) ボーン名2 平行移動2(x座標 y座標 z座標) クォータニオン(w x y z) ...
actions = Armature.NLA.GetActions() # list of actions
:
for a in actions:
act = actions[a]
out.write("A %s\n" % a)
frames = act.getFrameNumbers()
for f in frames: # repeat each key frame
writeKeyframe(out, f, arm_ob.getPose())
def writeKeyframe(out, frame, pose):
out.write("K %d" % frame)
Blender.Set('curframe', frame)
pbones = pose.bones.values()
for pb in pbones:
loc = pb.loc # location of this pose bone
quat = pb.quat # quaternion of this pose bone
out.write(" %s %f %f %f %f %f %f %f" % (pb.name, loc.x, loc.y, loc.z, quat.w, quat.x, quat.y, quat.z))
out.write("\n")
と、念のため、処理前のフレーム番号を保存しておいて、処理の終了時に、
curFrame = Blender.Get('curframe')
と、戻すようにしてます。
Blender.Set('curframe', curFrame)
モデル座標 → ワールド座標変換 → ボーン座標変換 → 移動/回転 → ワールド座標変換 → モデル座標変換
モデル座標(=ワールド座標) → ボーン座標変換 → 移動/回転 → モデル座標変換
モデル座標(=ワールド座標) → アーマチャ座標変換 → ボーン座標変換 → ボーン行列(移動/回転)適用 → アーマチャ座標変換 → モデル座標変換
モデル座標(=ワールド座標) → アーマチャ座標変換 → 前腕の行列変換 × 上腕の行列変換 → モデル座標変換
V x Am(-1) x Fm(-1) x Fr x Fm x Um(-1) x Ur x Um x Am
S アーマチャ名 アーマチャ行列
B レベル ボーン名 ボーン行列
if obj.parentType == Object.ParentTypes['ARMATURE']:
# armature
arm = arms[obj.getParent().name]
arm_ob = bpy.data.objects[arm.name]
m = arm_ob.mat # get matrix
out.write("S %s %f %f %f %f %f %f %f %f %f %f %f %f\n"
% (arm.name,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2],
m[3][0], m[3][1], m[3][2]))
level = 0
for k in arm.bones.keys():
b = arm.bones[k]
if not b.hasParent():
# root bone
writeBone(out, mesh, b, level)
:
def writeBone(out, mesh, bone, level):
m = bone.matrix['ARMATURESPACE']
out.write("B %d %s %f %f %f %f %f %f %f %f %f %f %f %f\n"
% (level, bone.name,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2],
m[3][0], m[3][1], m[3][2]))
if bone.hasChildren():
for b in bone.children:
writeBone(out, mesh, b, level+1)
void Model::draw() const {
int lastImgIdx = -1;
for (unsigned int faceIdx = 0; faceIdx < numFaces; faceIdx++) {
Face *f = &faces[faceIdx];
bool useTexture;
// テクスチャの準備
if (f->imgIdx >= 0) {
glEnable(GL_TEXTURE_2D);
if (f->imgIdx != lastImgIdx) {
const Image *img = images.at(f->imgIdx);
img->setup();
lastImgIdx = f->imgIdx;
}
useTexture = true;
} else {
glDisable(GL_TEXTURE_2D);
lastImgIdx = -1;
useTexture = false;
}
:
unsigned char *data;
と、ilCopyPixels()でコピーするようにしました。また、ilGenImages() で取得したimage(識別子)は画像のコピーが終わってしまえば不要なので、関数内で生成し、終わったら削除するようにしました。glTexImage2D()の呼び出しは次のように、
bool loadImage(const std::string& path) {
bool res;
ILuint image;
ilGenImages(1, &image);
ilBindImage(image);
if (ilLoadImage((wchar_t *)path.c_str())) {
width = ilGetInteger(IL_IMAGE_WIDTH);
height = ilGetInteger(IL_IMAGE_HEIGHT);
if(ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT) {
iluFlipImage();
}
data = new unsigned char[width * height * 4];
ilCopyPixels(0, 0, 0, width, height, 1, IL_RGBA, IL_UNSIGNED_BYTE, data);
res = true;
} else {
ILenum err = ilGetError();
std::cerr << iluErrorString(err) << std::endl;
res = false;
}
ilDeleteImage(image);
return res;
}
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
/**
* 画像行の読み込み
*/
void Model::readImageLine(std::istream& in) {
std::string file;
in >> file;
if (in.fail()) {
std::cerr << "failed in reading image line" << std::endl;
return;
}
Image *img = new Image(file);
images.push_back(img);
}
bool Image::loadImage(const std::string& path) {
ilBindImage(image);
if (ilLoadImage(path.c_str())) {
width = ilGetInteger(IL_IMAGE_WIDTH);
height = ilGetInteger(IL_IMAGE_HEIGHT);
if(ilGetInteger(IL_IMAGE_ORIGIN) != IL_ORIGIN_LOWER_LEFT) {
iluFlipImage();
}
return true;
} else {
ILenum err = ilGetError();
std::cerr << iluErrorString(err) << std::endl;
return false;
}
}
void Model::draw() const {
// 面の数だけ繰り返し
for (unsigned int faceIdx = 0; faceIdx < numFaces; faceIdx++) {
Face *f = &faces[faceIdx];
bool useTexture;
// テクスチャの準備
if (f->imgIdx >= 0) {
glEnable(GL_TEXTURE_2D);
const Image *img = images.at(f->imgIdx);
img->setup();
useTexture = true;
} else {
glDisable(GL_TEXTURE_2D);
useTexture = false;
}
// 描画
if (f->mtlIdx >= 0) {
const Material *mtl = &materials[f->mtlIdx];
glColor3f(mtl->r, mtl->g, mtl->b);
}
glBegin(GL_QUADS);
std::vector<unsigned int>::iterator itv;
for (unsigned int i=0; i<f->numVerts; i++) {
FaceVertex* fv = &f->verts[i];
if (useTexture) glTexCoord2f(fv->u, fv->v);
Vertex *v = &vertices [fv->idx];
glVertex3f(v->x, v->y, v->z);
}
glEnd();
}
}
void Image::setup() const {
ilBindImage(image);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
glBindTexture(GL_TEXTURE_2D, texName);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, ilGetData());
}
F マテリアル番号 頂点数 [頂点番号1 頂点番号2 頂点番号3 ...] [画像ファイル番号] [UV座標1 UV座標2 UV座標3 ...]
I 画像ファイル名
images = [] # list of texture file name
:
# repeat each faces
for f in mesh.faces:
out.write("F %d %d" % (f.mat, len(f.verts)))
for v in f.verts:
out.write(" %d" % v.index)
if mesh.faceUV and f.image:
texName = os.path.split(f.image.filename)[1]
try:
texNum = images.index(texName)
except ValueError:
images.append(texName)
texNum = len(images) - 1
out.write(" %d" % texNum)
for uv in f.uv:
out.write(" %f %f" % (uv[0], uv[1]))
out.write("\n")
# texture image files
for img in images:
out.write("I %s\n" % img)
O Dice 1 8 6
M 0.800000 0.800000 0.800000
V 1.998771 1.000000 -1.000000
V 1.998771 -1.000000 -1.000000
V -0.001229 -1.000000 -1.000000
V -0.001229 1.000000 -1.000000
V 1.998771 0.999999 1.000000
V 1.998770 -1.000001 1.000000
V -0.001229 -1.000000 1.000000
V -0.001229 1.000000 1.000000
F 0 4 0 1 2 3 0 0.673504 0.005739 0.673504 0.255311 0.336261 0.255311 0.336261 0.005739
F 0 4 4 7 6 5 0 0.673504 0.754455 0.336261 0.754455 0.336261 0.504883 0.673504 0.504883
F 0 4 0 4 5 1 0 1.010747 0.754455 0.673504 0.754455 0.673504 0.504883 1.010747 0.504883
F 0 4 1 5 6 2 0 0.673504 0.255311 0.673504 0.504883 0.336261 0.504883 0.336261 0.255311
F 0 4 2 6 7 3 0 -0.000982 0.504883 0.336261 0.504883 0.336261 0.754455 -0.000982 0.754455
F 0 4 4 0 3 7 0 0.673504 0.754455 0.673504 1.004027 0.336261 1.004027 0.336261 0.754455
I dice.png
M R値 G値 B値
F マテリアル番号 頂点数 頂点番号1 頂点番号2...
:
# マテリアルの数だけ繰り返し
for m in mesh.materials:
col = m.getRGBCol()
out.write("M %f %f %f\n" % (col[0], col[1], col[2]))
:
# 面の数だけ繰り返し
for f in mesh.faces:
out.write("F %d %d" % (f.mat, len(f.verts)))
for v in f.verts:
out.write(" %d" % v.index)
out.write("\n")
:
/**
* マテリアル行の読み込み
*/
void Model::readMaterialLine(std::istream& in) {
Matrial *m = materials[matCount++]; // MatrialはRGB値を保持する構造体
in >> m->r >> m->g >> m->b;
}
/**
* フェース行の読み込み
*/
void Model::readFaceLine(std::istream& in) {
Face* f = faces[faceCount++]; // Faceは面情報を保持する構造体
in > f->mat >> f->numVerts;
if (in.fail()) {
std::cerr << "failed in readling face info" << std::endl;
}
:
}
:
glBegin(GL_QUADS);
for (unsigned int i=0; i<faceCount; i++) {
Face* f = faces[i];
Material* m = materials[f->mat];
glColor3f(m->r, m->g, m->b);
for (unsigned int j=0; j<f->numVerts; j++) {
glVertex3f(v[faces[i]->verts[j]);
}
}
glEnd();