【cocos2d-x】スプライトを規則的に並べる為の静止中は常に視点を向くSprite3Dクラスの実装

[cocos2d-x]Sprite3D-静止中はビルボード

http://jp.cocos.com/

 

こんにちは。良昌です。

cocos2d-xのSprite3D。モデルさえ用意すれば、超簡単に3D機能を楽しめるので重宝しています。ただ、1つだけ難点があり、それを解決するのに結構はまっていました。

その難点とは、スプライトを画面中央以外に配置した場合は、テクスチャの側面が見えてしまうことです。側面が見えるのは3Dならではの仕様なので通常は効果的なのですが、スプライトを規則的に並べたい場合などは、配置によって幅、高さが変わってしまうため、整列させることが非常に困難です。

sprite3d

そこで今回は、静止中は常に視点(カメラ)を向くSprite3Dクラスを作ってみたので、紹介したいと思います。


静止中は常に視点を向くように、それ以外(例えば回転中など)は通常のSprite3Dのレンダリングを行いたいので、ステータスを判定するための変数、カメラの向きを保持するための変数を定義します。

// ステータスを判定するための列挙型
enum class State
{
    INIT,
    IDLE,
    ROTATE,
};
State _state = State::INIT;

// カメラの向きを保持するための変数
cocos2d::Vec3 _cameraDirection;

また、レンダリングの挙動を変更する必要があるので、cocos2d::Sprite3D::drawメソッドをオーバーライドします。

virtual void draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags);

create、initメソッドについては、用途に応じた定義をして下さい。これでヘッダーは完了です。


続いて実装に入ります。まずはdrawメソッドです。

void TestSprite3D::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags)
{
    auto matrix = transform;

    // (1)
    if (_state == State::INIT)
    {
        _state = State::IDLE;
        auto camera = Camera::getVisitingCamera();
        _cameraDirection = Vec3(matrix.m[12] - camera->getNodeToWorldTransform().m[12],
                                matrix.m[13] - camera->getNodeToWorldTransform().m[13],
                                matrix.m[14] - camera->getNodeToWorldTransform().m[14]);
        _cameraDirection.normalize();
    }

    // (2)
    if (_state == State::IDLE)
    {
        auto zRotateVecSize = sqrtf(powf(matrix.m[8], 2.f)
                                    + powf(matrix.m[9], 2.f) 
                                    + powf(matrix.m[10], 2.f));

        matrix.m[8] = _cameraDirection.x * zRotateVecSize;
        matrix.m[9] = _cameraDirection.y * zRotateVecSize;
    }
    
    // (3)
    Sprite3D::draw(renderer, matrix, flags);
}

(1)ではカメラの向きを単位ベクトルで保持しています。カメラの向きはレンダリング開始のタイミングで決定され、カメラが移動してもカメラとスプライトの法線上を動くので、平行移動時に関してはスプライトに対しての向きの絶対値は変わりません。drawメソッドの引数であるtransform(変数matrix)、及びCamera::getNodeToWorldTransform()メソッドの戻り値はMat4オブジェクト(4×4の行列)です。この行列の添え字が12、13、14である要素は平行移動時の位置座標を保持しており、これを利用してベクトルの減算で法線ベクトルを算出し、Vec3::normalize()メソッドで単位ベクトル(長さ1のベクトル)に正規化しています。

(2)がビルボード処理となります。Mat4オブジェクトの添え字が8、9、10である要素はZ軸回転時の位置座標を保持しています。このベクトルの大きさを算出(*1)し、x、y座標に(1)で算出した単位ベクトルを乗算し、視点へ向きを変換(*2)しています。
(*1)ベクトル(x, y, z)の大きさは(x^2 + y^2 + z^2)の平方根となります。
(*2)単位ベクトルに大きさを乗算することで、ベクトルの向きを変換できます。

最後に(3)で基底クラスのdrawメソッドを呼出し、レンダリングを行っています。


次にアクション(回転)時の実装です。ステータスの変更はアクション終了時に行う必要があるので、シーケンシャルに処理を実行します。

// (4)
_state = State::ROTATE;

runAction(Sequence::create(
    RotateBy::create(0.5f, Vec3(-180.f, 0.f, 0.f)), // (5) 
    CallFunc::create([this]
    {
        // (6)
        _state = State::IDLE;
        _cameraDirection.x *= -1;
        _cameraDirection.y *= -1;
    }
    ), NULL));

(4)でステータスをROTATEに変更し、(5)でX軸で-180度回転、回転完了後に(6)でステータスをIDLEにし、(1)で保持したカメラの向きを反転しています。


以下に完成後の動画を載せておきます。
http://youtu.be/9u1X9B8oEFQ

この問題を解決するために、かなりの時間を費やしWEB上を探し回ったのですが、有効な情報は得られませんでした。cocos2d-xのSprite3Dをこんな風に利用している方がほとんどいないのかも知れませんね。。。。もし、同じ悩みを抱えている方がいて、この記事が問題解決の糸口となれば幸いです。

では、また。

コメントを残す

メールアドレスが公開されることはありません。