平面点集三角化的示例

平面点集三角化的代码,虽然做了一些优化,但是效率还是非常低,在笔记本上跑,10K个点1秒左右,好处是代码好理解。源码如下:

 

package ImageProcess;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.geom.Point2D;
import java.awt.geom.Point2D.Double;
import java.awt.image.BufferedImage;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Comparator;

import javax.swing.JFrame;

public class Delaunay {
    public static long counter = 0;

    // 三角形的边:
    class IndexedEdge {
        public int p1;
        public int p2;
    }

    // 三角形:
    class IndexedTriangle {
        public int p1;
        public int p2;
        public int p3;
    }

    class Circle {
        public double xc;
        public double yc;
        public double rad;
    }

    // 顶点坐标:
    Point2D.Double[] points;
    int nPoint;

    // 三角形:
    IndexedTriangle[] triangles;
    int nTriangle;

    private static double EPS = 1.192092896e-07F;

    public static void main(String[] args) {
        window w = new window();
    }

    // 初始化:
    @SuppressWarnings("unchecked")
    public Delaunay(Point2D.Double[] verts) {
        int i;

        // 顶点数:
        nPoint = verts.length;

        // 总的三角数:
        nTriangle = 0;

        // 顶点数组:
        points = new Point2D.Double[nPoint + 3];
        for (i = 0; i < points.length; i++) {
            points[i] = new Point2D.Double();
            if (i < nPoint) {
                points[i].x = verts[i].x;
                points[i].y = verts[i].y;
            }
        }

        // 三角形数组:
        triangles = new IndexedTriangle[nPoint * 3];
        for (i = 0; i < triangles.length; i++) {
            triangles[i] = new IndexedTriangle();
        }

        // 顶点数组按横坐标排序:
        Arrays.sort(points, new Comparator<Point2D.Double>() {
            @Override
            public int compare(Double o1, Double o2) {
                return o1.x > o2.x ? 1 : -1;
            }
        });
    }

    /*
     * 检查点(px,py)是否在点(x1,y1),(x2,y2),(x3,y3)确定的圆内, 
     * 注意 : (px,py)点与其中一边张开的角小于该边的对角,就可以证明该点在三角形的外接圆的外面
     */
    public final boolean inCircle1(double px, double py, double x1, double y1, double x2, double y2, double x3,
            double y3) {

        double aa = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
        double bb = (x1 - x3) * (x1 - x3) + (y1 - y3) * (y1 - y3);
        double cc = (x2 - x3) * (x2 - x3) + (y2 - y3) * (y2 - y3);
        double AA = (px - x2) * (px - x2) + (py - y2) * (py - y2);
        double BB = (px - x3) * (px - x3) + (py - y3) * (py - y3);

        double a = Math.sqrt(aa);
        double b = Math.sqrt(bb);
        double A = Math.sqrt(AA);
        double B = Math.sqrt(BB);

        // 根据余弦定理计算两角的余弦值:
        double s = (aa + bb - cc) * (A * B);
        double r = (AA + BB - cc) * (a * b);

        // 如果不在同一边,则做一转换:
        double d = ((y1 - y3) * (x2 - x3) - (x1 - x3) * (y2 - y3)) * ((py - y3) * (x2 - x3) - (px - x3) * (y2 - y3));
        if (d < 0) {
            s *= -1;
        }

        // 如果点P对边的角度比该边的对角值大,则说明点在外接圆内:
        return s > r;
    }
    
    /**
     * 判断点(px,py)是否包含在(ax,ay),(bx,by),(cx,cy)三点确定圆内
     * @param px    p点的坐标
     * @param py
     * @param ax    三个点的坐标
     * @param ay
     * @param bx
     * @param by
     * @param cx
     * @param cy
     * @return
     */
    private boolean inCircle(double px, double py, double ax, double ay, double bx, double by, double cx, double cy) {
        double dx = ax - px;
        double dy = ay - py;
        double ex = bx - px;
        double ey = by - py;
        double fx = cx - px;
        double fy = cy - py;

        double ap = dx * dx + dy * dy;
        double bp = ex * ex + ey * ey;
        double cp = fx * fx + fy * fy;

        return dx * (ey * cp - bp * fy) -
               dy * (ex * cp - bp * fx) +
               ap * (ex * fy - ey * fx) < 0;
    }    

    /*
     * 检查p点是否在p1,p2,p3确定的圆内,并返回三点确定的圆的圆心和半径: 注意 : p点若在三角形的一条边上,则可确定其在圆内:
     */
    public boolean circumCircle(Point2D.Double p, Point2D.Double p1, Point2D.Double p2, Point2D.Double p3, Circle cir) {
        double m1, m2, mx1, mx2, my1, my2;
        double dx, dy, rsqr, drsqr;

        /* 检查是否共线 */
        if (Math.abs(p1.y - p2.y) < EPS && Math.abs(p2.y - p3.y) < EPS) {
            return false;
        }

        if (Math.abs(p2.y - p1.y) < EPS) {
            m2 = -(p3.x - p2.x) / (p3.y - p2.y);
            mx2 = (p2.x + p3.x) / 2.0;
            my2 = (p2.y + p3.y) / 2.0;
            cir.xc = (p2.x + p1.x) / 2.0;
            cir.yc = m2 * (cir.xc - mx2) + my2;
        } else if (Math.abs(p3.y - p2.y) < EPS) {
            m1 = -(p2.x - p1.x) / (p2.y - p1.y);
            mx1 = (p1.x + p2.x) / 2.0;
            my1 = (p1.y + p2.y) / 2.0;
            cir.xc = (p3.x + p2.x) / 2.0;
            cir.yc = m1 * (cir.xc - mx1) + my1;
        } else {
            m1 = -(p2.x - p1.x) / (p2.y - p1.y);
            m2 = -(p3.x - p2.x) / (p3.y - p2.y);
            mx1 = (p1.x + p2.x) / 2.0;
            mx2 = (p2.x + p3.x) / 2.0;
            my1 = (p1.y + p2.y) / 2.0;
            my2 = (p2.y + p3.y) / 2.0;
            cir.xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
            cir.yc = m1 * (cir.xc - mx1) + my1;
        }
        dx = p2.x - cir.xc;
        dy = p2.y - cir.yc;
        rsqr = dx * dx + dy * dy;
        cir.rad = Math.sqrt(rsqr);
        dx = p.x - cir.xc;
        dy = p.y - cir.yc;
        drsqr = dx * dx + dy * dy;
        return drsqr <= rsqr;
    }

    /**
     * 检查当前点的横坐标px与三个点确定的圆心的距离是否已大于圆的半径:
     * @param px    当前点的横坐标
     * @param p1x    三个顶点的坐标
     * @param p1y
     * @param p2x
     * @param p2y
     * @param p3x
     * @param p3y
     * @return
     */
    public final boolean isComplete(double px, double p1x, double p1y, double p2x, double p2y, double p3x, double p3y) {
        //counter++;
        double m1, m2, mx1, mx2, my1, my2;
        double dx, dy, rsqr;
        double xc, yc, rad;

        /* 检查是否共线 */
        if (Math.abs(p1y - p2y) < EPS && Math.abs(p2y - p3y) < EPS) {
            return false;
        }

        if (Math.abs(p2y - p1y) < EPS) {
            m2 = -(p3x - p2x) / (p3y - p2y);
            mx2 = (p2x + p3x) / 2.0;
            my2 = (p2y + p3y) / 2.0;
            xc = (p2x + p1x) / 2.0;
            yc = m2 * (xc - mx2) + my2;
        } else if (Math.abs(p3y - p2y) < EPS) {
            m1 = -(p2x - p1x) / (p2y - p1y);
            mx1 = (p1x + p2x) / 2.0;
            my1 = (p1y + p2y) / 2.0;
            xc = (p3x + p2x) / 2.0;
            yc = m1 * (xc - mx1) + my1;
        } else {
            m1 = -(p2x - p1x) / (p2y - p1y);
            m2 = -(p3x - p2x) / (p3y - p2y);
            mx1 = (p1x + p2x) / 2.0;
            mx2 = (p2x + p3x) / 2.0;
            my1 = (p1y + p2y) / 2.0;
            my2 = (p2y + p3y) / 2.0;
            xc = (m1 * mx1 - m2 * mx2 + my2 - my1) / (m1 - m2);
            yc = m1 * (xc - mx1) + my1;
        }
        dx = p2x - xc;
        dy = p2y - yc;
        rsqr = dx * dx + dy * dy;
        rad = Math.sqrt(rsqr);
        return px > rad + xc + EPS;
    }

    int Triangulate() {
        boolean[] complete;
        IndexedEdge[] edges;
        int nedge = 0; // 边的个数:
        int trimax; // 三角最大数
        int emax; // 边的最大数
        int i, j, k;
        double xp, yp, x1, y1, x2, y2, x3, y3, xc, yc, r;
        double xmin, xmax, ymin, ymax, xmid, ymid;
        double dx, dy, dmax;

        /* 为各变量分配内存: */
        trimax = 2 * (nPoint + 3) - 5;
        complete = new boolean[trimax];
        emax = 3 * (nPoint + 3) - 6;
        edges = new IndexedEdge[emax];
        for (i = 0; i < edges.length; i++) {
            edges[i] = new IndexedEdge();
        }
        /*
         * 寻找顶点的范围并构造超级三角形:
         */
        xmin = points[0].x;
        ymin = points[0].y;
        xmax = xmin;
        ymax = ymin;
        for (i = 1; i < nPoint; i++) {
            if (points[i].x < xmin) {
                xmin = points[i].x;
            }
            if (points[i].x > xmax) {
                xmax = points[i].x;
            }
            if (points[i].y < ymin) {
                ymin = points[i].y;
            }
            if (points[i].y > ymax) {
                ymax = points[i].y;
            }
        }
        dx = xmax - xmin;
        dy = ymax - ymin;
        dmax = (dx > dy) ? dx : dy;
        xmid = (xmax + xmin) / 2.0;
        ymid = (ymax + ymin) / 2.0;
        /*
         * 构造超级三角形,将该三角形的顶点添加到顶点数组的末尾, 并将该三角形放在三角形数组的起始位置:
         */
        points[nPoint + 0].x = xmid - 20 * dmax;
        points[nPoint + 0].y = ymid - dmax;
        points[nPoint + 1].x = xmid;
        points[nPoint + 1].y = ymid + 20 * dmax;
        points[nPoint + 2].x = xmid + 20 * dmax;
        points[nPoint + 2].y = ymid - dmax;

        //顺时针方向添加超级三角形:
        triangles[0].p1 = nPoint;
        triangles[0].p2 = nPoint + 1;
        triangles[0].p3 = nPoint + 2;
        complete[0] = false;
        nTriangle = 1;
        
        /*
         * 将所有点依次加入现有网格中:
         */
        for (i = 0; i < nPoint; i++) {
            // 取一个点:
            xp = points[i].x;
            yp = points[i].y;
            nedge = 0;

            /*
             * 建立边的缓存。如果有一点P落在三角形的外接圆内,那么将三角形的三条边放到缓存中,并移除这个三角形:
             */
            for (j = 0; j < nTriangle; j++) {
                if (complete[j]) {
                    continue;
                }

                IndexedTriangle curTri = triangles[j];
                x1 = points[curTri.p1].x;
                y1 = points[curTri.p1].y;
                x2 = points[curTri.p2].x;
                y2 = points[curTri.p2].y;
                x3 = points[curTri.p3].x;
                y3 = points[curTri.p3].y;

                // 如果点(xp,yp)在三个点确定的圆内,则重构三角形:
                if (inCircle(xp, yp, x1, y1, x2, y2, x3, y3)) {
                    // 缓存3条边:
                    edges[nedge + 0].p1 = curTri.p1;
                    edges[nedge + 0].p2 = curTri.p2;
                    edges[nedge + 1].p1 = curTri.p2;
                    edges[nedge + 1].p2 = curTri.p3;
                    edges[nedge + 2].p1 = curTri.p3;
                    edges[nedge + 2].p2 = curTri.p1;
                    nedge += 3;

                    // 还原三个点完成标记:
                    curTri.p1 = triangles[nTriangle - 1].p1;
                    curTri.p2 = triangles[nTriangle - 1].p2;
                    curTri.p3 = triangles[nTriangle - 1].p3;
                    complete[j] = false;

                    // 减去总数:
                    nTriangle--;
                    j--;
                } else {
                    //如果当前点到三角形的外接圆心的距离已经开始大于半径了,则后续点则无需比较:
                    complete[j] = this.isComplete(xp, x1, y1, x2, y2, x3, y3);
                }
            }
            
            /*
             * 标记公共边。如果两个共边的三角形的外接圆均包含该点,则此公共边在重构三角形时应该去掉,在这里先做上标记:
             */
            for (j = 0; j < nedge - 1; j++) {
                for (k = j + 1; k < nedge; k++) {
                    if ((edges[j].p1 == edges[k].p2) && (edges[j].p2 == edges[k].p1)) {
                        edges[j].p1 = -1;
                        edges[j].p2 = -1;
                        edges[k].p1 = -1;
                        edges[k].p2 = -1;
                    }
                }
            }
            
            /*
             * 跳过已经做了标记的边,构建三角形。所有的边按顺时针方向排列:
             */
            for (j = 0; j < nedge; j++) {
                if (edges[j].p1 < 0 || edges[j].p2 < 0)
                    continue;

                triangles[nTriangle].p1 = edges[j].p1;
                triangles[nTriangle].p2 = edges[j].p2;
                triangles[nTriangle].p3 = i;
                complete[nTriangle] = false;
                nTriangle++;
            }
        }
        /*
         * 移除和超级三角形三个顶点相连的三角形的边:
         */
        for (i = 0; i < nTriangle; i++) {
            if (triangles[i].p1 >= nPoint || triangles[i].p2 >= nPoint || triangles[i].p3 >= nPoint) {
                triangles[i].p1 = triangles[nTriangle - 1].p1;
                triangles[i].p2 = triangles[nTriangle - 1].p2;
                triangles[i].p3 = triangles[nTriangle - 1].p3;
                nTriangle--;
                i--;
            }
        }

        return 0;
    }

    public void showPoints(Graphics g) {
        int i, x, y;

        g.translate(20, 50);

        g.setColor(Color.RED);
        for (i = 0; i < nPoint; i++) {
            x = (int) Math.round(points[i].x);
            y = (int) Math.round(points[i].y);

            g.drawOval(x - 2, y - 2, 4, 4);
        }
        g.setColor(Color.black);
    }

    public void ShowTriangles(Graphics g) {
        int i, x1, y1, x2, y2, x3, y3;

        for (i = 0; i < nTriangle; i++) {
            x1 = (int) Math.round(points[triangles[i].p1].x);
            y1 = (int) Math.round(points[triangles[i].p1].y);
            x2 = (int) Math.round(points[triangles[i].p2].x);
            y2 = (int) Math.round(points[triangles[i].p2].y);
            x3 = (int) Math.round(points[triangles[i].p3].x);
            y3 = (int) Math.round(points[triangles[i].p3].y);

            g.drawLine(x1, y1, x2, y2);
            g.drawLine(x2, y2, x3, y3);
            g.drawLine(x3, y3, x1, y1);
        }
    }
}

/**用于显示的类*/

class window extends JFrame {
    public window() {
        this.setBounds(0, 0, 1200, 900);

        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.setVisible(true);
    }

    public void paint(Graphics g) {
        g.clearRect(0, 0, this.getWidth(), this.getHeight());

        BufferedImage bi = new BufferedImage(2000, 1000, BufferedImage.TYPE_3BYTE_BGR);
        Graphics gc = bi.getGraphics();
        gc.setColor(Color.white);
        gc.fillRect(0, 0, 2000, 1000);

        Point2D.Double[] lst = new Point2D.Double[10000];
        for (int i = 0; i < lst.length; i++) {
            lst[i] = new Point2D.Double(Math.random() * 800, Math.random() * 800);
        }

        Calendar t1 = Calendar.getInstance();
        Delaunay d = new Delaunay(lst);

        d.Triangulate();

        Calendar t2 = Calendar.getInstance();

        System.out.println("耗时:" + (t2.getTimeInMillis() - t1.getTimeInMillis()) + "ms");

        d.showPoints(gc);

        d.ShowTriangles(gc);

        g.drawImage(bi, 0, 0, null);

        System.out.println("size=" + d.nTriangle);

        System.out.println("判断" + Delaunay.counter + "次");
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值