Previous topic

BVHモーション再生

Next topic

PMD

This Page

ボーンの頂点配列化とスケルトンクラスの分離

今の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
inserted by FC2 system