
顔がないとちょっとホラーになってしまうので、とりあえずって感じで乗っけました。頂点数350ぐらい。超ローポリですな。
で、歩きのアクションを作成中です。
うーむ、こりゃ色気も何もないな。まいっか、今回の目的はそれじゃないし。
ゲームを作りたくてOpenGLや関連技術を勉強中です。ちなみに仕事は、同じIT系とはいえ業務系ソフト開発をかれこれ20年以上やってます。ゲーム開発...むずい...
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;
}
ボーンアニメーションのために、もう1つ出力しないといけない情報が残っていました。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::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();

F 頂点数 頂点番号1 頂点番号2 頂点番号3..
O オブジェクト名 頂点数 面数
:
for f in mesh.faces:
out.write("F %d" % len(f.verts))
for v in f.verts:
out.write(" %d" % v.index)
out.write("\n")
:
case 'F':
Face* f = faces[faceCount]; // Faceは面情報を保持する構造体
in >> f->numVerts;
if (in.fail()) break;
++faceCount;
f->verts = new int[f->numVerts];
for (unsigned int i=0; i<numVerts; i++) {
unsigned int v;
in >> v;
if (in.fail()) {
break;
} else {
f->verts[i] = v;
}
}
:
glBegin(GL_QUADS);
for (unsigned int i=0; i<faceCount; i++) {
Face* f = faces[i];
for (unsigned int j=0; j<f->numVerts; j++) {
glVertex3f(v[faces[i]->verts[j]);
}
}
glEnd();
V x座標1 y座標1 z座標1
V x座標2 y座標2 z座標2
:
def write(filename):
sce = Scene.getCurrent(); # 現在のシーン
obj = sce.objects.active # 選択中のオブジェクト
if not obj or obj.type != 'Mesh':
# 選択中のオブジェクトがメッシュ型でなければエラー表示して中止
Draw.PupMenu('not an mesh object')
return
mesh = obj.getData(mesh=True) # メッシュデータを取得
out = file(filename, "w") # ファイルを開く
for v in mesh.verts: # 頂点の数だけ繰り返し
out.write("V %f %f %f¥n" % (v.co.x, v.co.y, v.co.z))
Blender.Window.FileSelector(write, "Export")
#!BPY
# -*- coding: utf-8 -*-
"""
Name: 'MyModel'
Blender: 248
Group: 'Export'
Tooltip: 'MyModel exporter'
"""
import bpy
import Blender
from Blender import *
:
V -0.230893 1.000000 -1.000000
V -0.230893 -1.000000 -1.000000
V 12.300001 -0.572064 -1.000000
V 12.299997 0.572064 -1.000000
V -0.230895 0.999999 1.000000
V -0.230890 -1.000001 1.000000
V 12.300003 -0.572064 1.000000
V 12.299999 0.572064 1.000000
V 5.017009 0.752617 -1.000000
V 5.636728 0.752617 -1.000000
V 5.017009 -0.752617 -1.000000
V 5.636729 -0.752617 -1.000000
V 5.017009 0.752617 1.000000
V 5.636729 0.752617 1.000000
V 5.017009 -0.752618 1.000000
V 5.636729 -0.752617 1.000000
