Phaser 3 : 凹面の当たり判定を自動で生成してみる③ 輪郭追跡

前回に引き続き、凹面メッシュの自動生成に挑戦します。

取得したアルファ値をもとに輪郭追跡をしてみる

輪郭追跡処理アルゴリズム 画像処理ソリューションの記事を参考に輪郭追跡し画像の境界線を取得してみたいと思います。
ピクセルをわかりやすく扱うためにimageDataからpixelCopy配列を作成します。

for (let i = 0; i < this.imageData.height; i++) {
    this.pixelsCopy[i] = new Array();
    for (let j = 0; j < this.imageData.width; j++) {
        this.pixelsCopy[i].push(0)
    }
}

次に輪郭追跡の原点(画像のピクセルの中でy値が最小のピクセルのうちx値が最小のもの)を探します。
記事のサンプルでは上から順にピクセルを走査していますが、今回の画像はアルファ値でトリミングしてあるのでy座標は0で決め打ちしていいはずです。

let origin = 0;

while (this.pixels[origin][3] === 0) {
    origin++;
}

次に左下からピクセルのアルファ値を調べるGetBorder関数を作ります。

public GetBoarder(v: Phaser.Math.Vector2): Phaser.Math.Vector2 {

    //左下のピクセルから調べていく
    if (v.x > 0 && v.y < this.imageData.height - 1 ) {
        if (this.pixelsCopy[v.y + 1][v.x - 1] !== 0) {
            return new Phaser.Math.Vector2(v.x - 1, v.y + 1);
        }
    }
    //下
    if (v.y < this.imageData.height - 1 ) {
        if (this.pixelsCopy[v.y + 1][v.x] !== 0) {
            return new Phaser.Math.Vector2(v.x, v.y + 1);
        }
    }

    //右下
    if (v.x < this.imageData.width - 1 && v.y < this.imageData.height - 1) {
        if (this.pixelsCopy[v.y + 1][v.x + 1] !== 0) {
            return new Phaser.Math.Vector2(v.x + 1, v.y + 1);
        }
    }

    //右
    if (v.x < this.imageData.width - 1 ) {
        if (this.pixelsCopy[v.y][v.x + 1] !== 0) {
            return new Phaser.Math.Vector2(v.x + 1, v.y);
        }
    }

    //右上
    if (v.x < this.imageData.width - 1 && v.y > 0 ) {
        if (this.pixelsCopy[v.y - 1][v.x + 1] !== 0) {
            return new Phaser.Math.Vector2(v.x + 1, v.y - 1);
        }
    }

    //上
    if (v.y > 0 ) {
        if (this.pixelsCopy[v.y - 1][v.x] !== 0) {
            return new Phaser.Math.Vector2(v.x, v.y - 1);
        }
    }

    //左上
    if (v.x > 0 && v.y > 0) {
        if (this.pixelsCopy[v.y - 1][v.x - 1] !== 0 ) {
            return new Phaser.Math.Vector2(v.x - 1, v.y - 1);
        }
    }

    //左
    if (v.x > 0 ) {
        if (this.pixelsCopy[v.y][v.x - 1] !== 0) {
            return new Phaser.Math.Vector2(v.x - 1, v.y);
        }
    }
    return null;
}
}

originの位置から一回GetBorderを実行し、そのあとwhileでループさせます。
originの位置に戻ってくるまでやりたいのですが、無限ループになるといけないので適当に1000回ほど実行してみます。

tmp = this.GetBoarder(new Phaser.Math.Vector2(origin, 0));
let calc = 0;
while (calc < 1000) {

    tmp = this.GetBoarder(tmp);

    let posX = this.sys.canvas.width / 2;
    let posY = this.sys.canvas.height / 2;

    let dx = posX + tmp.x;
    let dy = posY + tmp.y;


    let pic = this.add.graphics();
    pic.x = dx;
    pic.y = dy;
    pic.fillStyle(0x494949, 1);
    pic.fillPointShape(new Phaser.Math.Vector2(0, 0), 1);

    console.log(tmp.x,tmp.y,origin,mov)

    calc++
}

実行結果がこちらです。 f:id:Phaser_3:20190109164009p:plain 右下で途切れてしまっています。
原因を調べてみると、左下から追跡を開始しているので、左下が透明でないピクセルの位置でループしてしまっていました。
GetBorder関数を変更し、元の位置のピクセルには戻らないようにしてみました。

let mov = new Phaser.Math.Vector2(0, 0);
tmp = this.GetBoarder(new Phaser.Math.Vector2(origin, 0), mov);
let calc = 0;
while (calc < 1000) {

    tmp = this.GetBoarder(tmp,mov);

    let posX = this.sys.canvas.width / 2;
    let posY = this.sys.canvas.height / 2;

    let dx = posX + tmp.x;
    let dy = posY + tmp.y;


    let pic = this.add.graphics();
    pic.x = dx;
    pic.y = dy;
    pic.fillStyle(0x494949, 1);
    pic.fillPointShape(new Phaser.Math.Vector2(0, 0), 1);

    console.log(tmp.x,tmp.y,origin,mov)

    calc++
}
~~
public GetBoarder(v: Phaser.Math.Vector2, m: Phaser.Math.Vector2): Phaser.Math.Vector2 {

    //左下のピクセルから調べていく
    if (v.x > 0 && v.y < this.imageData.height - 1 && !(m.x == 1 && m.y == -1)) {
        if (this.pixelsCopy[v.y + 1][v.x - 1] !== 0) {
            m.set(-1, 1);
            return new Phaser.Math.Vector2(v.x - 1, v.y + 1);
        }
    }
    //下
    if (v.y < this.imageData.height - 1 && !(m.x == 0 && m.y == -1)) {
        if (this.pixelsCopy[v.y + 1][v.x] !== 0) {
            m.set(0,1);
            return new Phaser.Math.Vector2(v.x, v.y + 1);
        }
    }

    //右下
    if (v.x < this.imageData.width - 1 && v.y < this.imageData.height - 1 && !(m.x == -1 && m.y == -1)) {
        if (this.pixelsCopy[v.y + 1][v.x + 1] !== 0) {
            m.set(1,1);
            return new Phaser.Math.Vector2(v.x + 1, v.y + 1);
        }
    }

    //右
    if (v.x < this.imageData.width - 1 && !(m.x == -1 && m.y == 0)) {
        if (this.pixelsCopy[v.y][v.x + 1] !== 0) {
            m.set(1,0);
            return new Phaser.Math.Vector2(v.x + 1, v.y);
        }
    }

    //右上
    if (v.x < this.imageData.width - 1 && v.y > 0 && !(m.x == -1 && m.y == 1)) {
        if (this.pixelsCopy[v.y - 1][v.x + 1] !== 0) {
            m.set(1,-1);
            return new Phaser.Math.Vector2(v.x + 1, v.y - 1);
        }
    }

    //上
    if (v.y > 0 && !(m.x == 0 && m.y == 1)) {
        if (this.pixelsCopy[v.y - 1][v.x] !== 0) {
            m.set(0,-1);
            return new Phaser.Math.Vector2(v.x, v.y - 1);
        }
    }

    //左上
    if (v.x > 0 && v.y > 0 && !(m.x == 1 && m.y == 1)) {
        if (this.pixelsCopy[v.y - 1][v.x - 1] !== 0 ) {
            m.set(-1,-1);
            return new Phaser.Math.Vector2(v.x - 1, v.y - 1);
        }
    }

    //左
    if (v.x > 0 && !(m.x == 1 && m.y == 0)) {
        if (this.pixelsCopy[v.y][v.x - 1] !== 0) {
            m.set(-1.0);
            return new Phaser.Math.Vector2(v.x - 1, v.y);
        }
    }
    return null;
}

実行結果がこちらです。 f:id:Phaser_3:20190109164534p:plain 今度は線が上に向かうところで途切れてしまっています。
調べてみると、真上のピクセルに移動した直後に左下のピクセルを追跡してしまっているため、右下の輪郭近辺をぐるぐるとループしてしまっていました。
すべてのピクセルにおいて左下から検索してしまうと不都合があるようです。
引き続き輪郭追跡について実装していきます。