Showing posts with label Delaunay 分割. Show all posts
Showing posts with label Delaunay 分割. Show all posts

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


});

前回、canvas にランダムな点を撒いて移動させるところまでやったので、やっとお目当ての Delaunay(ドロネー)分割に入れる。 Delaunay 分割の仕組みについては、素晴らしい記事があったのでそちらを参照されたい。 さて、じゃあさっそくソースコード。 以下、JavaScript だけ。

$(document).ready(function () {

    var SAMPLE = {};

    SAMPLE.Main = (function () {
        
        var canJqObj = $("#can"),
            canDom = document.getElementById('can'),
            ctx = document.getElementById('can').getContext("2d");
        
        // ==================================================
        // 頂点
        // ==================================================
        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 () {

                var drawLine = function (vs, vd) {

                    // パスのリセット
                    ctx.beginPath();
                    // 線の太さ
                    ctx.lineWidth = 1;
                    // 線の色
                    ctx.strokeStyle = "rgb(162, 206, 226)";
                    // 開始位置
                    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.drawCircle = function () {

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

            }; // drawCircle

            // ==================================================
            // 指定された頂点が外接円の内側にあるかどうかを返す
            // ==================================================
            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


        } // 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

        
        // ==================================================
        // 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 < 10; 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 drawVertexList(list) {

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

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

                // 頂点を描画
                list[i].draw();

            }

        } // drawVertexList

        // ==================================================
        // 指定された座標を包含する円に外接する三角形を取得
        // ==================================================
        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) {

            for (var i in triangleList) {

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

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

            }

        } // drawTriangleList

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

            var vertexList,
                triangleList;

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

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

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

                // canvas をクリア
                clearCanvas();

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

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

                // 三角形の要素数をセット
                var count = 0;
                for (var i in triangleList)
                    count++;

                // ※確認用
                $("#triangleListLength").text("triangleList.length : " + triangleList.length);
                $("#triangleCount").text("triangle count : " + count);

                // 頂点リストを描画
                drawVertexList(vertexList);

            }, 33);

        } // init

        // 開始
        init();

    })(); // Main


});

確認用で頂点、三角形、三角形の外接円とその中心が描画されます。分割できた途端満足しちゃったので配列からでっかい三角形も除去してないです。 参考記事「Tercel::Diary: ProcessingでDelaunay分割(実装篇)」とちょっと違うのは三角形の重複判定あたり。エッジを定義して判定する方が個人的にはわかり易かったので参考記事とは別の方法で判定してます。
前回は canvas にランダムな点を描画したけど、今回はその点を移動させてみようと思う。 おおまかな構造は前回からほとんど変わっていない。主にいじった箇所は Vertex あたりか。今回は点が移動するので、移動速度を格納する velocityX, velocityY を追加した。それと動かない点も欲しかったから isStatic も追加。これをもとにその点を動かすかどうかを判定する。velocityX, velocityY, isStatic はすべて Vertex 生成時に初期化する。 各点を移動するために Vertex に update を追加。isStatic が偽になるものは velocityX, velocityY をもとに座標を更新する。update は draw する前にコールする。 今回は点を移動させるので一度だけ描画して終わりってわけにはいかない。描画処理を一定時間で繰り返すために drawVertexList を setInterval で括る。 こんなとこでしょうか。結果は Result から。 以下、JavaScript だけ。

$(document).ready(function () {

    var SAMPLE = {};

    SAMPLE.Main = (function () {

        // 頂点
        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;
                }

            };
        }

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

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

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

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

            // ランダムに頂点を追加(移動)
            for (i = 0; i < 10; i++) {
                list.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));
            }

        }

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

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

        }

        // 描画リストを描画
        function drawVertexList(list) {

            // canvas をクリア
            clearCanvas();

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

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

                // 頂点を描画
                list[i].draw();

            }
        }

        // 開始
        function init() {

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

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

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

                // 頂点リストを描画
                drawVertexList(vertexList);

            }, 33);
        }

        // 開始
        init();

    })();

});

前回、Delaunay(ドロネー)分割の下準備を行ったけど今回もそれの続き。 内容は前回からほとんど変わっていない。追加したのは initializeVertexList と drawVertexList くらいか。Vertex を配列で格納する変数を定義しておいてあとは initialize で初期化して、draw で描画するといった感じだ。四隅以外の頂点はランダムで生成して push。あとは配列をぐるぐる。 ソースコードは以下となる。 以下、JavaScript だけ。

$(document).ready(function () {

    var SAMPLE = {};

    SAMPLE.Main = (function () {

        // 頂点
        function Vertex(x, y) {
            this.x = x;
            this.y = y;
            
            // 描画
            this.draw = function(){
                
                ctx.beginPath();
                ctx.arc(this.x, this.y, 2, 0, 360, true);
                ctx.fill();
                
            };
        }
        
        var canJqObj = $("#can"),
            canDom = document.getElementById('can'),
            ctx = document.getElementById('can').getContext("2d"),
            vertexList = [];

        // 頂点リストを初期化
        function initializeVertexList(list)
        {
            // 四隅に頂点を追加
            list.push(new Vertex(0, 0));
            list.push(new Vertex(canDom.width, 0));
            list.push(new Vertex(0, canDom.height));
            list.push(new Vertex(canDom.width, canDom.height));
            
            // ランダムに頂点を追加
            for(i = 0; i <= 10; i++)
            {
                list.push(new Vertex(
                    Math.floor(Math.random() * canDom.width),
                    Math.floor(Math.random() * canDom.height)));
            }
        }
        
        // 頂点リストを描画
        function drawVertexList(list)
        {
            for (i = 0; i < list.length; i++)
            {
                list[i].draw();
            }
        }
        
        // 開始
        function init() {

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

            // 頂点リストを初期化
            initializeVertexList(vertexList);
            
            // 頂点リストを描画
            drawVertexList(vertexList);
        }

        // 開始
        init();

    })();

});

Feb 25, 2014

Delaunay(ドロネー)分割の勉強。まずは基本的な描画(線、円)とドロネー分割で必要な外接円の求め方から。 以下、JavaScript だけ。

$(document).ready(function () {

    var SAMPLE = {};

    SAMPLE.Main = (function () {

        // 頂点
        function Vertex(x, y) {
            this.x = x;
            this.y = y;
        }

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

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

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

                var drawLine = function (vs, vd) {
                    // パスのリセット
                    ctx.beginPath();
                    // 線の太さ
                    ctx.lineWidth = 1;
                    // 線の色
                    ctx.strokeStyle = "#454545";
                    // 開始位置
                    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.drawCircle = function () {

                // 外接円の求め方
                var x1 = this.v0.x,
                    y1 = this.v0.y,
                    x2 = this.v1.x,
                    y2 = this.v1.y,
                    x3 = this.v2.x,
                    y3 = this.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 = Math.sqrt((dx * dx) + (dy * dy)),    // 外接円の半径
                    circle = new Circle(center, radius);

                // 外接円を描画
                circle.draw();
                
                // 外接円の中心を描画
                circle.drawCenter();
            };
        }

        // 円
        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();
                
            };

        }

        var canJqObj = $("#can"),
            canDom = document.getElementById('can'),
            ctx = document.getElementById('can').getContext("2d"),
            // 三角形の定義
            triangle = new Triangle(
            new Vertex(100, 120),
            new Vertex(220, 270),
            new Vertex(300, 160));

        // 開始
        function init() {

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

            // 三角形を描画
            triangle.draw();
            
            // 円を描画
            triangle.drawCircle();
        }

        // 開始
        init();

    })();


});