Meshを継承したカスタムDisplayObjectを作る:Triangle(補足)


先の記事 Meshを継承したカスタムDisplayObjectを作る:Triangleの補足およびTriangleクラスの更新です。


デモとソースも更新されています。demo2 および ソース

Textureの余白設定

TexturePackerなど、画像パーツの余白をトリムしてパッキングしてくれるツールを使うと、1枚のテクスチャアトラスにたくさんの画像が配置できるので、大変に効率的です。Starling2上では余白情報付きのテクスチャが扱え、frameプロパティというRectangle型で情報を保持しています。このframe情報付きのテクスチャを表示した場合に、1つ前の投稿ではBounds情報が余白なしの値を返していました。これを、修正してみました。

修正されたTriangleクラス
https://github.com/harayoki/-qiita1/blob/triangle_demo2/src/harayoki/starling/display/Triangle.as


デモでは、余白を図示してみました、画像のふちの薄い白い部分です

実装詳細

解説します。また、例によってQuadの実装を調整して修正しています。

sample.as
public override function getBounds(targetSpace:DisplayObject, out:Rectangle=null):Rectangle
{
    if (out == null) out = new Rectangle();

    // 自身の座標で計算する場合の最適化
    if (targetSpace == this)
    {
        out.copyFrom(_bounds);
    }
    // 親の座標系で回転がない場合最適化(通常ここがアクセスされることが多そう)
    // TriangleでもQuadのまま使えるように頂点を配置しているのでそのまま使える
    else if (targetSpace == parent && rotation == 0.0)
    {
        var scaleX:Number = this.scaleX;
        var scaleY:Number = this.scaleY;

        out.setTo(   x - pivotX * scaleX,     y - pivotY * scaleY,
            _bounds.width * scaleX, _bounds.height * scaleY);

        if (scaleX < 0) { out.width  *= -1; out.x -= out.width;  }
        if (scaleY < 0) { out.height *= -1; out.y -= out.height; }
    }
    // 3Dの場合はとりあえず対応しない
    //else if (is3D && stage) // not implemented currently
    //{
    //  stage.getCameraPosition(targetSpace, sPoint3D);
    //  getTransformationMatrix3D(targetSpace, sMatrix3D);
    //  RectangleUtil.getBoundsProjected(_bounds, sMatrix3D, sPoint3D, out);
    //}
    else
    {
        getTransformationMatrix(targetSpace, sMatrix);
        _getTriangleBounds(_bounds, sMatrix, out)
    }
}

↑getBoundsの実装ですが、最後のelse節まではコメント以外Quadそのままです。最適化の処理が目立ちますが、getBoundsはwidthやheightプロパティにアクセスするだけで内部的に呼び出されるメソッドなので、十分に速くする必要があるのでしょう。逆に言うと不用意にwidth、heightにアクセスしてはいけないということですね。気をつけよう。。
3Dについては扱ったことがないので、今回は対応しないことにしました。

最後のelse節の部分は、Quadの実装を調整しなくてはいけなかったので、見通しがいいように↓別メソッドに抜き出しました。

sample.as
private function _getTriangleBounds(rectangle:Rectangle, matrix:Matrix, out:Rectangle):void {

    var minX:Number = Number.MAX_VALUE, maxX:Number = -Number.MAX_VALUE;
    var minY:Number = Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE;
    _getPositions(rectangle, sPositions);

    for (var i:int=0; i<3; ++i)
    {
        MatrixUtil.transformCoords(matrix, sPositions[i].x, sPositions[i].y, sPoint);

        if (minX > sPoint.x) minX = sPoint.x;
        if (maxX < sPoint.x) maxX = sPoint.x;
        if (minY > sPoint.y) minY = sPoint.y;
        if (maxY < sPoint.y) maxY = sPoint.y;
    }

    out.setTo(minX, minY, maxX - minX, maxY - minY);
}

↑三角形のBoundsを計算する部分です。内部保持している矩形の_bounds情報から頂点として採用している3点を _getPositionsメソッドで抜き出し、MatrixUtil.transformCoordsメソッドで現在の拡大・縮小・移動・回転状態を適用した値にします。そこから最大値、最小値を抜き出して、bounds情報とします。

sample.as
private function _getPositions(rectangle:Rectangle, out:Vector.<Point>):void
{
    out[0].x = rectangle.left;  out[0].y = rectangle.top;
    out[1].x = rectangle.right; out[1].y = rectangle.top;
    out[2].x = rectangle.left;  out[2].y = rectangle.bottom;
}

↑矩形の_bounds情報から3頂点を抜き出している部分です。

以上の実装で、余白付きのテクスチャを扱う場合でも正しく bounds情報が取得できるようになります。

こうしてみると、難解ではないにしても、処理がずいぶん重たそうです。余白があるテクスチャの場合のちょっとおかしな挙動は仕様としてしまうのも十分ありに思えます。前回の処理のままとするか、思い切ってダミー値しか返さないという選択をするのもあり得るのかなと、、。(もちろん、フレームワークとして世に出すならば、きちんと実装をすべきでしょう。)

Triangle.fromTexture()とnew Triangle()

テクスチャを適用したTriangleインスタンスを作るには、2通りの方法があります。

sample.as
// textureとともにインスタンス化
var tr1:Triangle = Triangle.fromTexture(someTexture);

// newしてからtextureを設定
var tr2:Triangle = new Triangle(100,100);
tr2.texture = someTexture;

何が違うのかというと、前者ではテクスチャの大きさ・形状のままのインスタンスができるのに対して、後者ではコンストラクタにわたした縦横の幅によっては歪んだ見た目のインスタンスができます。tr2.readjustSize()というメソッド後から呼ぶことで、前者と同じ状態になります。そして、これはQuad.fromTexture()とnew Quad()にも同じことが言えます。(そのような仕様でTriangleを作ったので。) ここまではQuadのソースを読むとコメントで書いてあることなのですが、この記事を書いている最中に、テクスチャのフレーミングが後者の方法では適用されないことに気づきました。余白が少ない場合、なかなか気づきづらいので、要注意ポイントかもしれません。

おわりに

テクスチャの余白を扱うと実装が難解になります。Starling2.0のいろいろな箇所のソースコードを眺めてもそれぞれ苦労しているのがみてとれます。フレームワークを作るのは大変だなとつくづく思うところです。多くの機能を持たないほうが速くはなるので、使いずらくても速度を優先する仕様のDisplayObjectは自身で作るとよいのでしょうね。

ということで作りますかね。