Mar 17, 2014

前回やっと Delaunay 分割まで辿り着いた。お目当ての Delaunay 分割も片付いたので、あとは軽く仕上げるだけ。今回は Delaunay 分割した三角形を塗りたいと思います。 さっそくソースコード。 予め canvas のサイズをもとに色テーブルを作っておいて、Delaunay 分割した三角形の外接円の中心をみて色を決めます。んで、塗る。以下、JavaScript だけ。

$(document).ready(function () {

    var SAMPLE = {};

    SAMPLE.Main = (function () {

        var canJqObj = $("#can"),
            canDom = document.getElementById('can'),
            ctx = document.getElementById('can').getContext("2d"),

            // 色のコレクション(最低4色)
            colors = [
            // http://www.colourlovers.com/palette/2490997/HJ_Project_Palette
            ["#000000", "#FF0080", "#00D4ED", "#D2D2D2", "#FFFFFF"],
            // http://www.colourlovers.com/palette/2490986/all_night_long_.
            ["#ED146F", "#EDDE45", "#61D2D6", "#242424", "#FAFAFA"],
            // http://www.colourlovers.com/palette/2490977/SILVIA_PELISSERO_APC
            ["#110E19", "#B60F19", "#F86F07", "#FF9F33", "#C1D8A0"],
            // http://www.colourlovers.com/palette/2490968/navy_blend
            ["#14343C", "#DDE915", "#1C15E9", "#A83C17", "#159797"],
            // http://www.colourlovers.com/palette/2491000/w_a_t_e_r_m_e_l_o_n
            ["#E9625C", "#B5C368", "#0A3D5D", "#CCE9C7", "#C9D88A"],

            //http://www.colourlovers.com/palette/1483942/The_Autumn_Queen_HPP
            ["#4E2E1E", "#EA4F6C", "#F99548", "#FCCA55"],
            //http://www.colourlovers.com/palette/1297074/Autumn_Rainbow
            ["#6D2243", "#EC5E0C", "#F78F1E", "#85871A"],
            //http://www.colourlovers.com/palette/36998/french_roast
            ["#A7321C", "#FFDC68", "#928941", "#352504"],
            //http://www.colourlovers.com/palette/582235/October_Roads
            ["#821F29", "#F6E03F", "#ADBD06", "#D46600"], ],

            // 使用する色をランダムに選定
            colorIndex = Math.floor(Math.random() * (colors.length - 1));


        // ==================================================
        // 頂点
        // ==================================================
        function Vertex(x, y, vx, vy) {

            this.x = x;
            this.y = y;

            // 固定頂点かどうか(true:固定、false:移動)
            this.isStatic = ((vx == undefined || vy == undefined) ? true : false);
            // X 軸の移動速度
            this.velocityX = ((vx == undefined) ? Math.random() * 0.7 - 0.35 : vx);
            // Y 軸の移動速度
            this.velocityY = ((vy == undefined) ? Math.random() * 0.7 - 0.35 : vy);

            // ==================================================
            // 頂点を描画
            // ==================================================
            this.draw = function () {

                // 固定は黒、移動は赤
                ctx.fillStyle = (this.isStatic ? "rgb(66, 66, 66)" : "rgb(255, 66, 22)");

                ctx.beginPath();
                ctx.arc(this.x, this.y, 2, 0, 360, true);
                ctx.fill();

            };

            // ==================================================
            // 頂点を更新
            // ==================================================
            this.update = function () {

                // 移動する頂点の場合
                if (!this.isStatic) {

                    // canvas の端で跳ね返す(X)
                    if (0 > this.x || canDom.width < this.x) {
                        this.velocityX *= -1;
                    }

                    // canvas の端で跳ね返す(Y)
                    if (0 > this.y || canDom.height < this.y) {
                        this.velocityY *= -1;
                    }

                    // 座標を更新
                    this.x += this.velocityX;
                    this.y += this.velocityY;
                }

            };

        } // Vertex


        // ==================================================
        // エッジ
        // ==================================================
        function Edge(v0, v1) {

            this.v0 = v0;
            this.v1 = v1;

        } // Edge


        // ==================================================
        // 三角形
        // ==================================================
        function Triangle(v0, v1, v2) {

            // 外接円の求め方
            var x1 = v0.x,
                y1 = v0.y,
                x2 = v1.x,
                y2 = v1.y,
                x3 = v2.x,
                y3 = v2.y,
                c = 2.0 * ((x2 - x1) * (y3 - y1) - (y2 - y1) * (x3 - x1)),
                x = ((y3 - y1) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) + (y1 - y2) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1)) / c,
                y = ((x1 - x3) * (x2 * x2 - x1 * x1 + y2 * y2 - y1 * y1) + (x2 - x1) * (x3 * x3 - x1 * x1 + y3 * y3 - y1 * y1)) / c,
                center = new Vertex(x, y), // 外接円の中心
                dx = center.x - v0.x,
                dy = center.y - v0.y,
                radius_squared = (dx * dx) + (dy * dy),
                radius = Math.sqrt(radius_squared), // 外接円の半径
                circle = new Circle(center, radius);

            this.v0 = v0;
            this.v1 = v1;
            this.v2 = v2;
            this.circle = circle;
            this.radius_squared = radius_squared;

            // ==================================================
            // 三角形を描画
            // ==================================================
            this.draw = function (colorTable) {

                if (colorTable != undefined) {
                    // 色を格納・設定
                    var rgb = colorTable.getColor(this.circle.center);
                    ctx.fillStyle = "rgb(" + rgb.R + ", " + rgb.G + ", " + rgb.B + ")";
                    // パスのリセット・三角形を指定
                    ctx.beginPath();
                    ctx.moveTo(v0.x, v0.y);
                    ctx.lineTo(v1.x, v1.y);
                    ctx.lineTo(v2.x, v2.y);
                    ctx.closePath();
                    // 塗る
                    ctx.fill();
                }

                var drawLine = function (vs, vd) {

                    var lineColor = "rgb(200, 200, 200)";

                    if (4 < colors[colorIndex].length) {
                        var lineRgb = new RGB(colors[colorIndex][4]);
                        lineColor = "rgb(" + lineRgb.R + ", " + lineRgb.G + ", " + lineRgb.B + ")";
                    }


                    // パスのリセット
                    ctx.beginPath();
                    // 線の太さ
                    ctx.lineWidth = 1;
                    // 線の色
                    ctx.strokeStyle = lineColor;
                    // 線を設定
                    ctx.moveTo(vs.x, vs.y);
                    ctx.lineTo(vd.x, vd.y);
                    // 描画
                    ctx.stroke();
                };

                drawLine(this.v0, this.v1);
                drawLine(this.v1, this.v2);
                drawLine(this.v2, this.v0);

            };

            // ==================================================
            // 指定された頂点が外接円の内側にあるかどうかを返す
            // ==================================================
            this.InCircumcircle = function (v) {

                var dx = this.circle.center.x - v.x,
                    dy = this.circle.center.y - v.y,
                    dist_squared = dx * dx + dy * dy;

                return (dist_squared <= this.radius_squared);

            }; // InCircumcircle

            // ==================================================
            // 外接円を描画
            // ==================================================
            this.drawCircle = function () {

                // 外接円の色
                ctx.strokeStyle = "rgb(191, 214, 191)";
                // 外接円を描画
                this.circle.draw();
                // 外接円の中心の色
                ctx.fillStyle = "rgb(191, 214, 191)";
                // 外接円の中心を描画
                this.circle.drawCenter();

            }; // drawCircle


        } // Triangle


        // ==================================================
        // 円
        // ==================================================
        function Circle(center, radius) {

            // 中心座標と半径  
            this.center = center;
            this.radius = radius;

            // ==================================================
            // 円を書く
            // ==================================================
            this.draw = function () {

                ctx.beginPath();
                ctx.arc(this.center.x, this.center.y, this.radius, 0, 360, true);
                ctx.stroke();

            };

            // ==================================================
            // 円の中心を書く
            // ==================================================
            this.drawCenter = function () {

                ctx.beginPath();
                ctx.arc(this.center.x, this.center.y, 2, 0, 360, true);
                ctx.fill();

            };

        } // Circle


        // ==================================================
        // RGB
        // ==================================================
        function RGB(hex) {
            var h = hex.substring(1, 7);
            return {
                R: parseInt(h.substring(0, 2), 16),
                G: parseInt(h.substring(2, 4), 16),
                B: parseInt(h.substring(4, 6), 16)
            }
        } // RGB


        // ==================================================
        // 色テーブル
        // ==================================================
        function ColorTable() {

            var colorTable = [],
                columnW = 20,
                columnH = 15;

            // ==================================================
            // 指定された頂点の色を取得
            // ==================================================
            this.getColor = function (v) {

                if (v == undefined) return new RGB("#000000");

                // 座標を補正
                var adjust = function (p, limit) {

                    var absP = Math.abs(p);
                    if (limit < absP) {
                        return (absP - (limit * Math.floor(absP / limit)));
                    } else {
                        return absP;
                    }

                };

                v.x = adjust(v.x, canDom.width);
                v.y = adjust(v.y, canDom.height);

                for (var i in colorTable) {
                    if (colorTable[i].start.x <= v.x && colorTable[i].end.x >= v.x && colorTable[i].start.y <= v.y && colorTable[i].end.y >= v.y) {
                        return colorTable[i].color;
                    }
                }

                return new RGB("#000000");

            }; // getColor

            // ==================================================
            // 色テーブルを描画(デバッグ用)
            // ==================================================
            this.draw = function () {

                for (var i in colorTable) {

                    ctx.fillStyle = "rgb(" + colorTable[i].color.R + ", " + colorTable[i].color.G + ", " + colorTable[i].color.B + ")";
                    ctx.fillRect(colorTable[i].start.x,
                    colorTable[i].start.y,
                    colorTable[i].end.x - colorTable[i].start.x,
                    colorTable[i].end.y - colorTable[i].start.y);
                }

            }; // draw

            // ==================================================
            // 色テーブルを初期化
            // ==================================================
            function initialize() {

                var x = 0,
                    y = 0;

                for (var i = 0; i < Math.ceil(canDom.width / columnW); i++) {

                    y = 0;
                    for (var j = 0; j < Math.ceil(canDom.height / columnH); j++) {
                        var col = Math.floor(Math.random() * 4);

                        colorTable.push({
                            color: new RGB(colors[colorIndex][col]),
                            start: new Vertex(x, y),
                            end: new Vertex(x + columnW, y + columnH)
                        });

                        y += columnH;
                    }
                    x += columnW;
                }

            } // initialize

            // 初期化
            initialize();

        } // ColorTable


        // ==================================================
        // canvas をクリア
        // ==================================================
        function clearCanvas() {

            ctx.clearRect(0, 0, canDom.width, canDom.height);
            ctx.globalAlpha = 1;

        } // clearCanvas

        // ==================================================
        // 頂点リストを初期化
        // ==================================================
        function initializeVertexList() {

            var vertexList = [];

            // 四隅に頂点を追加
            vertexList.push(new Vertex(0, 0));
            vertexList.push(new Vertex(canDom.width, 0));
            vertexList.push(new Vertex(0, canDom.height));
            vertexList.push(new Vertex(canDom.width, canDom.height));

            // ランダムに頂点を追加(固定)
            for (var i = 0; i < 10; i++) {
                vertexList.push(new Vertex(
                Math.floor(Math.random() * canDom.width),
                Math.floor(Math.random() * canDom.height)));
            }

            // ランダムに頂点を追加(移動)
            for (var j = 0; j < 40; j++) {
                vertexList.push(new Vertex(
                Math.floor(Math.random() * canDom.width),
                Math.floor(Math.random() * canDom.height),
                Math.random() * 0.7 - 0.35,
                Math.random() * 0.7 - 0.35));
            }

            return vertexList;

        } // initializeVertexList

        // ==================================================
        // 頂点リストを更新
        // ==================================================
        function updateVertexList(list) {

            for (var i = 0; i < list.length; i++) {

                // 頂点を更新
                list[i].update();

                // 頂点を描画(デバッグ用)
                //list[i].draw();

            }

        } // updateVertexList

        // ==================================================
        // 指定された座標を包含する円に外接する三角形を取得
        // ==================================================
        function getHugeTriangle(start, end) {

            // start が左上、end が右下になるように補正
            if (end.x < start.x) {
                var xTmp = start.x;
                start.x = end.x;
                end.x = xTmp;
            }
            if (end.y < start.y) {
                var yTmp = start.y;
                start.y = end.y;
                end.y = yTmp;
            }

            // 四角形を描画(デバッグ用)
            //ctx.strokeRect(start.x, start.y, end.x - start.x, end.y - start.y)

            // 渡された座標を包含する円を求める
            var center = new Vertex(((end.x - start.x) / 2.0) + start.x, ((end.y - start.y) / 2.0) + start.y),
                dx = center.x - start.x,
                dy = center.y - start.y,
                radius = Math.sqrt((dx * dx) + (dy * dy));

            // 円を描画(デバッグ用)
            //(new Circle(center, radius)).draw();
            //(new Circle(center, radius)).drawCenter();

            // その円に外接する正三角形を求める
            var x1 = center.x - Math.sqrt(3) * radius,
                y1 = center.y - radius,
                v1 = new Vertex(x1, y1),

                x2 = center.x + Math.sqrt(3) * radius,
                y2 = center.y - radius,
                v2 = new Vertex(x2, y2),

                x3 = center.x,
                y3 = center.y + 2 * radius,
                v3 = new Vertex(x3, y3);

            return new Triangle(v1, v2, v3);

        } // getHugeTriangle

        // ==================================================
        // 三角形のリストを初期化
        // ==================================================
        function initializeTriangleList(vertexList) {

            var triangleList = [];

            // canvas を包含する円に外接する三角形を格納
            triangleList.push(getHugeTriangle(new Vertex(0, 0), new Vertex(canDom.width, canDom.height)));

            // 1つずつ頂点を追加していく
            for (var i = 0; i < vertexList.length; i++) {
                var vertex = vertexList[i];
                AddVertex(vertex, triangleList);
            }

            return triangleList;

        } // initializeTriangleList

        // ==================================================
        // 三角形のリストに頂点を追加
        // ==================================================
        function AddVertex(vertex, triangleList) {

            // エッジリスト
            var edgeList = [];

            for (var i in triangleList) {
                var triangle = triangleList[i];

                // 追加する頂点が外接円の内側にある場合
                if (triangle.InCircumcircle(vertex)) {

                    // エッジに分解
                    edgeList.push(new Edge(triangle.v0, triangle.v1));
                    edgeList.push(new Edge(triangle.v1, triangle.v2));
                    edgeList.push(new Edge(triangle.v2, triangle.v0));

                    // 三角形のリストから削除
                    delete triangleList[i];
                }
            }

            // 分解したエッジの中からユニークなエッジを取得
            // 重複したエッジを除外することで不正な三角形が除外される
            // (ドロネー分割において不正な三角形が重複する特性を利用している)
            edgeList = UniqueEdges(edgeList);

            // ユニークなエッジをもとに三角形を生成
            for (var j in edgeList) {
                var edge = edgeList[j];

                // 追加する頂点とユニークなエッジをもとに新しい三角形を生成
                triangleList.push(new Triangle(edge.v0, edge.v1, vertex));
            }

        } // AddVertex

        // ==================================================
        // ユニークなエッジを取得
        // ==================================================
        function UniqueEdges(edgeList) {

            var uniqueEdges = [];

            for (var i in edgeList) {
                var edge1 = edgeList[i];
                var unique = true;

                for (var j in edgeList) {
                    if (i != j) {
                        var edge2 = edgeList[j];

                        // 重複したエッジの場合
                        if ((edge1.v0 == edge2.v0 && edge1.v1 == edge2.v1) || (edge1.v0 == edge2.v1 && edge1.v1 == edge2.v0)) {
                            unique = false;
                            break;
                        }
                    }
                }

                // ユニークなものだけ格納
                if (unique) {
                    uniqueEdges.push(edge1);
                }
            }

            return uniqueEdges;

        } // UniqueEdges

        // ==================================================
        // 三角形リストを描画
        // ==================================================
        function drawTriangleList(triangleList, colorTable) {

            for (var i in triangleList) {

                // 三角形を描画
                triangleList[i].draw(colorTable);

                // 外接円を描画
                //triangleList[i].drawCircle();

            }

        } // drawTriangleList

        // ==================================================
        // 開始
        // ==================================================
        function init() {

            var colorTable,
            vertexList,
            triangleList;

            // canvas のサイズを指定        
            canDom.width = 400;
            canDom.height = 350;

            // 色テーブルを初期化
            colorTable = new ColorTable();

            // 頂点リストを初期化
            vertexList = initializeVertexList();

            // 一定時間で繰り返す
            setInterval(function () {

                // canvas をクリア
                clearCanvas();

                // ドロネー分割
                triangleList = initializeTriangleList(vertexList);

                // 三角形リストを描画
                drawTriangleList(triangleList, colorTable);

                // 色テーブルを描画(デバッグ用)
                //colorTable.draw();

                // 頂点リストを更新
                updateVertexList(vertexList);

            }, 33);

        } // init

        // 開始
        init();

    })(); // Main


});

0 Comments :

Post a Comment