Top | 戻る

四角形の自由変形

  主に3D効果で用いられる自由変形手法の定番です。 任意の4点に囲まれた四角形を、別の4点に囲まれた四角形に変形します。
  まず始めに、 三角形a(a0,a1,a2)を別の三角形b(b0,b1,b2)に変形する行列を作成します。 行列の作成は複雑そうですが、ベクトルと行列を使うと三角関数を使わずに四則演算のみで計算できます。 行列は、
  1. O(原点)へ移動
  2. 単位行列に変換(aの座標系の逆行列)
  3. bの座標系に変換
  4. b0へ移動
といった4変換の組み合わせで作成します(文末のソース createMatrix 関数)。
図1 三角形を変換するために必要なベクトル
実行結果 (三角形の自由変形)
  四角形を変形する場合は、四角形を2つの三角形に分割してから各々の三角形を自由変形します。 しかし、四角形が平行四辺形でない場合は三角形の境界で画像が折れ曲がってしまいます(図2)。
実行結果 (四角形の自由変形/三角形×2)
図2 対角線が折れ曲がってしまう
  折れ曲がりを解消するために、あらかじめ四角形を複数の四角形に分割してから各々の四角形を自由変形します。 分割した四角形は元の四角形よりも平行四辺形に近くなるため、スムーズな変形が可能になります(図3)。
  転送元を短形のビットマップとして転送先を変形するのが一般的ですが、転送先を短形として転送元を変形するほうが意外性がある画像が得られるようです。
図3 四角形を分割
実行結果 (四角形の自由変形/縦横4分割, 三角形×32)
TransformUtil.as
package {

    import flash.geom.Point;
    import flash.geom.Matrix;
    import flash.display.Graphics;
    import flash.display.BitmapData;
    
    /**
     * TransformUtil
     * @author Kazuhiko Arase
     */

    public class TransformUtil {

        /**
         * ビットマップの三角形を描く。 
         * <br/>0 - 1
         * <br/>| /  
         * <br/>2 
         * @param g グラフィックス
         * @param bitmapData ビットマップデータ
         * @param a0 転送元(ビットマップデータ)の座標
         * @param a1 転送元(ビットマップデータ)の座標
         * @param a2 転送元(ビットマップデータ)の座標
         * @param b0 転送先(グラフィック)の座標
         * @param b1 転送先(グラフィック)の座標
         * @param b2 転送先(グラフィック)の座標
         */

        public static function drawBitmapTriangle(
            g : Graphics, bitmapData : BitmapData,
            a0 : Point, a1 : Point, a2 : Point, 
            b0 : Point, b1 : Point, b2 : Point
        ) : void {
            var matrix : Matrix = createMatrix(a0, a1, a2, b0, b1, b2);
            g.beginBitmapFill(bitmapData, matrix);
            drawTriangle(g, a0, a1, a2, matrix);
            g.endFill();
        }

        /**
         * ビットマップの四角形を描く。 
         * <br/>0 - 1
         * <br/>|   |
         * <br/>2 - 3
         * @param g グラフィックス
         * @param bitmapData ビットマップデータ
         * @param a0 転送元(ビットマップデータ)の座標
         * @param a1 転送元(ビットマップデータ)の座標
         * @param a2 転送元(ビットマップデータ)の座標
         * @param a3 転送元(ビットマップデータ)の座標
         * @param b0 転送先(グラフィック)の座標
         * @param b1 転送先(グラフィック)の座標
         * @param b2 転送先(グラフィック)の座標
         * @param b3 転送先(グラフィック)の座標
         * @param hDiv 水平方向の分割数
         * @param vDiv 垂直方向の分割数
         */

        public static function drawBitmapQuadrangle(
            g : Graphics, bitmapData : BitmapData,
            a0 : Point, a1 : Point, a2 : Point, a3 : Point, 
            b0 : Point, b1 : Point, b2 : Point, b3 : Point,
            hDiv : int = 4, vDiv : int = 4
        ) : void {

            for (var h : int = 0; h < hDiv; h++) {

                var h0 : Number = h / hDiv;
                var h1 : Number = (h + 1) / hDiv;

                var ta0 : Point = getPoint(a1, a0, h0);
                var ta1 : Point = getPoint(a1, a0, h1);
                var ta2 : Point = getPoint(a3, a2, h0);
                var ta3 : Point = getPoint(a3, a2, h1);

                var tb0 : Point = getPoint(b1, b0, h0);
                var tb1 : Point = getPoint(b1, b0, h1);
                var tb2 : Point = getPoint(b3, b2, h0);
                var tb3 : Point = getPoint(b3, b2, h1);

                for (var v : int = 0; v < vDiv; v++) {

                    var v0 : Number = v / vDiv;
                    var v1 : Number = (v + 1) / vDiv;

                    var tta0 : Point = getPoint(ta1, ta0, v0);
                    var tta1 : Point = getPoint(ta1, ta0, v1);
                    var tta2 : Point = getPoint(ta3, ta2, v0);
                    var tta3 : Point = getPoint(ta3, ta2, v1);
    
                    var ttb0 : Point = getPoint(tb1, tb0, v0);
                    var ttb1 : Point = getPoint(tb1, tb0, v1);
                    var ttb2 : Point = getPoint(tb3, tb2, v0);
                    var ttb3 : Point = getPoint(tb3, tb2, v1);

                    drawBitmapTriangle(g, bitmapData,
                        tta0, tta1, tta2, ttb0, ttb1, ttb2);
                    drawBitmapTriangle(g, bitmapData,
                        tta3, tta1, tta2, ttb3, ttb1, ttb2);
                }
            }
        }

        private static function getPoint(p0 : Point, p1 : Point, ratio : Number) : Point {
            return new Point(
                p0.x + (p1.x - p0.x) * ratio,
                p0.y + (p1.y - p0.y) * ratio
            );
        }

        private static function createMatrix(
            a0 : Point, a1 : Point, a2 : Point, 
            b0 : Point, b1 : Point, b2 : Point
        ) : Matrix {

            var ma : Matrix = new Matrix(
                a1.x - a0.x, a1.y - a0.y,
                a2.x - a0.x, a2.y - a0.y);
            ma.invert();

            var mb : Matrix = new Matrix(
                b1.x - b0.x, b1.y - b0.y,
                b2.x - b0.x, b2.y - b0.y);

            var m : Matrix = new Matrix();

            // O(原点)へ移動 
            m.translate(-a0.x, -a0.y);

            // 単位行列に変換(aの座標系の逆行列)
            m.concat(ma);

            // bの座標系に変換 
            m.concat(mb);

            // b0へ移動 
            m.translate(b0.x, b0.y);

            return m;
        }

        private static function drawTriangle(
            g : Graphics,
            p0 : Point, p1 : Point, p2 : Point,
            matrix : Matrix
        ) : void {

            p0 = matrix.transformPoint(p0);
            p1 = matrix.transformPoint(p1);
            p2 = matrix.transformPoint(p2);

            g.moveTo(p0.x, p0.y);
            g.lineTo(p1.x, p1.y);
            g.lineTo(p2.x, p2.y);
            g.lineTo(p0.x, p0.y);
        }        
    }
}

Contents Copyright © Kazuhiko Arase