今のOpenGLの行列スタックを利用したやり方は、本来の目的であるメッシュのアニメーションには使えない。ボーンの分かれ方とメッシュの分かれ方が異なるからだ。むしろワンスキンアニメーションといわれるようにメッシュは分かれていなかったりする。 そこで、ボーンをいったんひとつの頂点配列に格納してから個々の頂点にボーン変形を適用する形に実装方法を変更する。 BVHarray.py
#!/usr/bin/python
# coding: utf-8
import numpy
import math
import skeleton
import bvhloader
import rigid
def eulerMatrixZXY(x, y, z):
"""
euler angle ZXY
"""
cx=math.cos(bvhloader.to_radian(x))
cy=math.cos(bvhloader.to_radian(y))
cz=math.cos(bvhloader.to_radian(z))
sx=math.sin(bvhloader.to_radian(x))
sy=math.sin(bvhloader.to_radian(y))
sz=math.sin(bvhloader.to_radian(z))
return numpy.array([
[cy*cz-sx*sy*sz, cy*sz+sx*sy*cz, -cx*sy+sx],
[-sz*cx, cz*cx, sx],
[sy*cz+sx*cy*sz, sy*sz-sx*cy*cz, cx*cy],
], 'f')
class BVHMotion(object):
"""
BVHのモーションを抜き出す
"""
def getMatrix(self, bone, frame):
return self.getRigid(bone, frame).getMatrix()
def getRigid(self, bone, frame):
return rigid.Rigid(
self.getRotationMatrix(bone, frame),
self.getTranslation(bone, frame))
def getRotationMatrix(self, joint, frame):
"""
回転チャンネルの行列を返す
"""
return eulerMatrixZXY(
joint.channelMap[bvhloader.ChannelRotationX].getValue(frame),
joint.channelMap[bvhloader.ChannelRotationY].getValue(frame),
joint.channelMap[bvhloader.ChannelRotationZ].getValue(frame))
def getTranslation(self, joint, frame):
"""
移動チャンネルの移動ベクトルを返す
"""
return [
joint.channelMap[bvhloader.ChannelPositionX].getValue(frame),
joint.channelMap[bvhloader.ChannelPositionY].getValue(frame),
joint.channelMap[bvhloader.ChannelPositionZ].getValue(frame)]
class Skeleton(skeleton.Skeleton):
def onKeyDown(self, key):
if key==' ':
self.toggle()
elif key=='n':
self.advanceFrame(1)
elif key=='p':
self.advanceFrame(-1)
elif key=='r':
self.setFrame(0)
elif key=='b':
self.toggleBind()
def skeletonJoint(j):
joint=skeleton.Joint(j.name)
joint.offset=j.offset
return joint
def _rebuild(node, newParent):
newNode=skeletonJoint(node)
newParent.addChild(newNode)
for c in node.children:
_rebuild(c, newNode)
def rebuild(root):
newRoot=skeletonJoint(root)
for c in root.children:
_rebuild(c, newRoot)
return newRoot
if __name__=="__main__":
import sys
import glbase
import pg_ui
import Rokuro
import bvhloader
if len(sys.argv)<2:
print "usage: %s {bvh file}" % sys.argv[0]
sys.exit()
l=bvhloader.load(sys.argv[1])
if not l:
print "fail to load", sys.argv[1]
sys.exit()
print l
bvhSkeleton=skeleton.Skeleton()
bvhSkeleton.root=l.root
bvhSkeleton.motion=BVHMotion()
bvhSkeleton.frame_interval=l.frame_interval*1000
bvhSkeleton.frame_count=l.frame_count
bvhSkeleton.setup()
pg_ui.run(glbase.BaseController(Rokuro.RokuroView(100), bvhSkeleton))
スケルトンクラス skeleton.py
#!/usr/bin/python
# coding: utf-8
import math
import numpy
from OpenGL.GL import *
from OpenGL.GLU import *
from rigid import Rigid
class EmptyMotion(object):
def getMatrix(self, *_):
return numpy.identity(4)
def getRigid(self, *_):
return Rigid()
class Skeleton(object):
def __init__(self):
# アニメショーン経過時間
self.elaps=0
# 再生中のフレーム
self.currentFrame=0
# 停止フラグ
self.pause=False
# バインドポーズフラグ
self.bind=False
# 描画フラグ
self.isVisible=True
# オリジナルの頂点配列
self.vertices=[]
# 変形後の頂点配列
self.deformed=[]
# ボーン描画用線インデックス
self.lineIndices=[]
# ジョイント描画用点インデックス
self.pointIndices=[]
# 頂点・ジョイントの参照
self.vertexToJoint={}
self.jointToIndex={}
self.offsetMatrix={}
# ボーンの変換行列
self.matrices={}
# モーション
self.motion=EmptyMotion()
# 各ボーンの姿勢
self.currentMap={}
def setup(self):
# 頂点配列作成
self.buildVertexArray(self.root, [0, 0, 0])
self.buildIndex(self.root)
# numpy置き換え
self.vertices=numpy.array(self.vertices, 'f')
self.deformed=numpy.array(self.vertices, 'f')
self.lineIndices=numpy.array(self.lineIndices, 'u4')
self.pointIndices=numpy.array(self.pointIndices, 'u4')
def buildVertexArray(self, bone, offset):
head=[l+r for l, r in zip(offset, bone.offset)]
# このジョイントに対応する頂点のインデックスを記録
index=len(self.vertices)
self.vertices.append(head+[1])
# ジョイントにオフセット行列記録する
self.offsetMatrix[bone]=Rigid(pos=[-e for e in head]).getMatrix()
# 頂点とジョイントの参照
self.jointToIndex[bone]=index
self.vertexToJoint[index]=bone
for child in bone.children:
self.buildVertexArray(child, offset)
def buildIndex(self, bone):
for child in bone.children:
# ジョイント描画用点インデックス
self.pointIndices.append(self.jointToIndex[child])
# ボーン線描画用インデックス
self.lineIndices.append(self.jointToIndex[bone])
self.lineIndices.append(self.jointToIndex[child])
self.buildIndex(child)
def onUpdate(self, interval):
if not self.pause:
self.elaps+=interval
self.currentFrame=int(self.elaps/self.frame_interval) % self.frame_count
if self.bind:
pass
else:
# 変換行列を再帰的に計算する
#print self.currentFrame
self.updateMotion(self.root)
self.calcMatrix(self.root)
self.deform()
def updateMotion(self, bone):
transform=self.motion.getRigid(bone, self.currentFrame)
transform.translate(bone.offset)
self.currentMap[bone]=transform.getMatrix()
for child in bone.children:
self.updateMotion(child)
def calcMatrix(self, bone, acum=numpy.identity(4)):
matrix=numpy.dot(self.currentMap[bone], acum)
self.matrices[bone]=matrix
for child in bone.children:
self.calcMatrix(child, matrix)
def deform(self):
"""
各頂点に変換行列を適用した結果を取得する
"""
for i, v in enumerate(self.vertices):
bone=self.vertexToJoint[i]
self.deformed[i]=numpy.dot(v, numpy.dot(
self.offsetMatrix[bone], self.matrices[bone]))
def draw(self):
if not self.isVisible:
return
glEnableClientState(GL_VERTEX_ARRAY)
if self.bind:
# オリジナルの頂点配列
glVertexPointer(4, GL_FLOAT, 0, self.vertices)
else:
# 変換済みの頂点配列
glVertexPointer(4, GL_FLOAT, 0, self.deformed)
# DrawEdge
glColor(1, 1, 1, 1)
glDrawElements(GL_LINES, len(self.lineIndices),
GL_UNSIGNED_INT, self.lineIndices)
# DrawPoints
glColor(1, 0, 0, 1)
glPointSize(5)
glDrawElements(GL_POINTS, len(self.pointIndices),
GL_UNSIGNED_INT, self.pointIndices)
glDisableClientState(GL_VERTEX_ARRAY)
def toggle(self):
self.pause=not self.pause
def advanceFrame(self, d):
self.currentFrame+=d
if self.currentFrame<0:
self.currentFrame=0
if self.currentFrame>=self.frame_count:
self.frame_count-1
def setFrame(self, frame):
self.currentFrame=frame
if self.currentFrame<0:
self.currentFrame=0
if self.currentFrame>=self.frame_count:
self.frame_count-1
def toggleBind(self):
self.bind=not self.bind
def toggleDraw(self):
self.isVisible=not self.isVisible