QuadEdge双边坐标始终是原点

时间:2017-01-28 00:31:46

标签: javascript computational-geometry triangulation voronoi delaunay

我试图在Javascript中实现一种分而治之的算法来计算Delanay三角剖分,以获得Voronoi图。编码基于Graphic Gems IV中描述的C ++实现,而后者又基于1985年着名的Guibas和Stolfi论文。它使用了四边数据结构。

算法将点集分成两半;然后,它分别对这些部分进行三角测量;最后它继续“结婚”两半。

问题在于,它不起作用。当“结合”两半时,它会陷入无限递归。我认为问题可能出在四边形结构本身,因为第一个边缘和它的目标边缘具有相同的坐标,其双重和反向旋转边缘指向(0,0)。

这是代码(C ++的翻译不是100%,我请求帮助,因为我还是JS菜鸟):

    //The data structure starts here.
    var Edge = function (num, qe) {
    var _this = this;
    this.num = num;
    this.qe = qe;
    this.coord = new paper.Point();

    this.sym = function () {
        return (_this.num < 2) ? _this.qe[_this.num + 2] : this.qe[_this.num - 2];
    };

    this.oNext = function () {
        return this.next;
    };

    this.setNext = function (next) {
        this.next = next
    };

    this.rot = function () {
        return (this.num < 3) ? _this.qe[_this.num + 1] : _this.qe[_this.num - 3];
    };

    this.invRot = function () {
        return (_this.num > 0) ? _this.qe[_this.num - 1] : _this.qe[_this.num + 3];
    };

    this.oPrev = function () {
        return _this.rot().oNext().rot();
    };

    this.dNext = function () {
        return _this.sym().next.sym();
    };

    this.dPrev = function () {
        return _this.invRot().oNext().invRot();
    };

    this.lNext = function () {
        return _this.invRot().oNext().rot();
    };

    this.lPrev = function () {
        return _this.oNext().sym();
    };

    this.rNext = function () {
        return _this.rot().oNext().invRot();
    };

    this.rPrev = function () {
        return _this.sym().oNext();
    };

    this.dest = function () {
        return _this.sym().coord;
    };

    this.endPoints = function (or, de) {
        this.coord = or;
        this.sym().coord = de;
    };
};

//The first edge always points to itself
//as in edge.oNext().coord === edge.coord
var QuadEdge = function () {
    this.edges = new Array(4);
    for (var i = 0; i < 4; i++) {
        this.edges[i] = new Edge(i, this.edges);
    }
    this.edges[0].setNext(this.edges[0]);
    this.edges[1].setNext(this.edges[3]);
    this.edges[2].setNext(this.edges[2]);
    this.edges[3].setNext(this.edges[1]);
};
//Constructs and returns a new QuadEdge.
function makeEdge() {
    var qe = new QuadEdge();
    return qe.edges[0];
}
//Helper function, returns twice of the area of the triangle formed
//by a, b and c. Negative if the triangle is clockwise.
function triArea(a, b, c) {
    return (b.x - a.x) * (c.y - a.y) - (c.x - a.x) * (b.y - a.y);
}
//Tests if the point d is inside the circumcircle of the triangle formed by
//a, b, and c. As described by Guibas and Stolfi in their paper.
function inCircle(a, b, c, d) {
    return (Math.pow(a.x, 2) + Math.pow(a.y, 2)) * triArea(b, c, d) -
        (Math.pow(b.x, 2) + Math.pow(b.y, 2)) * triArea(a, c, d) +
        (Math.pow(c.x, 2) + Math.pow(c.y, 2)) * triArea(a, b, d) -
        (Math.pow(d.x, 2) + Math.pow(d.y, 2)) * triArea(a, b, d) > 0;
}
//Tests if triangle is in counterclockwise order.
function cClockwise(a, b, c) {
    return triArea(a, b, c) > 0;
}
//Tests if point is left of the provided edge.
function leftOf(point, edge) {
    return cClockwise(point, edge.coord, edge.dest());
}
//Tests if point is right of the provided edge.
function rightOf(point, edge) {
    return cClockwise(point, edge.dest(), edge.coord);
}
//If a and b are distinct, splice will combine them; if not, it will
//separate them in two. 
function splice(a, b) {
    var alpha = a.oNext().rot();
    var beta = b.oNext().rot();

    var t1 = b.oNext();
    var t2 = a.oNext();
    var t3 = beta.oNext();
    var t4 = alpha.oNext();

    a.setNext(t1);
    b.setNext(t2);
    alpha.setNext(t3);
    beta.setNext(t4);
}

function deleteEdge(e) {
    splice(e, e.oPrev());
    splice(e.sym(), e.sym().oPrev());
}

function connect(e1, e2) {
    var e = makeEdge();
    e.endPoints(e1.dest(), e2.coord);
    splice(e, e1.lNext());
    splice(e.sym(), e2);
    return e;
}

//Fixed thanks to: http://www.rpenalva.com/blog/?p=74
function valid(e, basel) {
    return rightOf(e.dest(), basel);
}
//This is the actual algorithm.
function divideAndConquer(vertices) {
    if (vertices.length === 2) {
        var a = makeEdge();
        a.coord = vertices[0];
        a.sym().coord = vertices[1];
        return {right: a, left: a.sym()};
    }
    else if (vertices.length === 3) {
        var a = makeEdge(), b = makeEdge();
        splice(a.sym(), b);
        a.coord = vertices[0];
        b.coord = vertices[1];
        a.sym().coord = b.coord;
        b.sym().coord = vertices[2];

        if (cClockwise(vertices[0], vertices[1], vertices[2])) {
            connect(b, a);
            return {right: a, left: b.sym()};
        }
        else if (cClockwise(vertices[0], vertices[2], vertices[1])) {
            var c = connect(b, a);
            return {right: c.sym(), left: c};
        }
        else
            return {right: a, left: b.sym()};
    }
    else if (vertices.length >= 4) {
        var half = Math.floor(vertices.length / 2);
        var lObjects = divideAndConquer(vertices.slice(0, half));
        var rObjects = divideAndConquer(vertices.slice(half, vertices.length));
        var ldo = lObjects.right, ldi = lObjects.left;
        var rdi = rObjects.right, rdo = rObjects.left;

        while (true) {
            if (leftOf(rdi.coord, ldi))
                ldi = ldi.lNext();
            else if (rightOf(ldi.coord, rdi))
                rdi = rdi.rPrev();
            else
                break;
        }

        var basel = connect(rdi.sym(), ldi);
        if (ldi.coord === ldo.coord)
            ldo = basel.sym();
        if (rdi.coord === rdo.coord)
            rdo = basel;
        while (true) {
            var lcand = basel.sym().oNext();
            if (valid(lcand, basel)) {
                while (inCircle(basel.dest(), basel.coord, lcand.dest(), lcand.oPrev().dest())) {
                    var t = lcand.oNext();
                    deleteEdge(lcand);
                    lcand = t;
                }
            }
            var rcand = basel.oPrev();
            if (valid(rcand, basel)) {
                while (inCircle(basel.dest(), basel.coord, rcand.dest(), rcand.oPrev().dest())) {
                    var t = rcand.oPrev();
                    deleteEdge(rcand);
                    rcand = t;
                }
            }
            //This is the part where it gets stuck,
            //it never reaches this condition.
            if (!valid(lcand, basel) && !valid(rcand, basel)) {
                break;
            }
            if (!valid(lcand, basel) || (valid(rcand, basel) && inCircle(lcand.dest(), lcand.coord, rcand.coord, rcand.dest()))) {
                basel = connect(rcand, basel.sym());
            }
            else {
                basel = connect(basel.sym(), lcand.sym());
            }
        }
        return {right: ldo, left: rdo};
    }
}

点类来自PaperJS

编辑:设置Codepen说明问题;单击输出窗口以添加点。添加第四个点时会发生递归。

EDIT 2:修复了由有效函数引起的无限递归,这要归功于Ruben Penalva blog上的代码。但是,指向原点的双边问题仍然存在。那就是:

edge.rot().coord === (0, 0)

这让我更加想到问题在于QuadEdge结构本身。

编辑3:第n次仔细阅读代码,我意识到旋转和反向旋转边缘的坐标永远不会设置。所以我尝试通过旋转原始边缘来设置它们。输出现在绘制某些东西,虽然我很确定这不是Delaunay三角剖分。我用进度更新了Codepen示例。

0 个答案:

没有答案