You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java实现Bowyer-Watson算法时Bad Triangles识别异常求助

Bowyer-Watson三角剖分算法Bad Triangles识别异常问题

我正在进行一项实现Bowyer-Watson三角剖分算法的学校项目。当前代码可生成正确的三角网并正确移除超级三角形,但在识别和移除Bad Triangles时出现异常:要么完全不移除,要么全部移除。Bad Triangles定义为外接圆包含当前点的三角形,我怀疑问题出在点是否在三角形外接圆内的判断逻辑或计算上,已困扰许久,恳请提供解决方案。

代码参考自Scotty Anderson的仓库,执行效果存在以下异常:

  • 不移除任何Bad Triangles
  • 移除全部Bad Triangles
  • 外接圆圆心计算结果异常

相关代码

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class Triangulation {
    private List<Point> points;
    private List<Triangle> triangles;
    private Point superPoint1, superPoint2, superPoint3;

    public Triangulation(List<Point> points) { 
        this.points = points;
        this.triangles = new ArrayList<>();
    }

    public List<Triangle> getTriangulation() {
        triangles.add(createSuperTriangle());
        List<Triangle> badTriangles = new ArrayList<>();

        for (int pIndex = 0; pIndex < points.size(); pIndex++) {
            badTriangles.clear();
            Point point = points.get(pIndex);

            // 识别Bad Triangles
            for (int triIndex = triangles.size() - 1; triIndex >= 0; triIndex--) {
                Triangle triangle = triangles.get(triIndex);

                double xDelta = point.x - triangle.circumCentre().x;
                double yDelta = point.y - triangle.circumCentre().y;
                double distance = Math.hypot(yDelta, xDelta);
                
                // 原判断逻辑:外接圆包含当前点的三角形为Bad Triangle
                if (distance < triangle.circumRadius()) {
                    badTriangles.add(triangle);
                }
            }

            List<Edge> polygon = new ArrayList<>();
            
            // 构建多边形:只保留Bad Triangles中不重复的边
            for (int i = 0; i < badTriangles.size(); i++) {
                Triangle triangle = badTriangles.get(i);
                List<Edge> edges = triangle.getEdges();

                for (int j = 0; j < edges.size(); j++) {
                    boolean badEdge = false;
                    for (int t = 0; t < badTriangles.size(); t++) {
                        if (t != i && badTriangles.get(t).ContainsEdge(edges.get(j))) {
                            badEdge = true;
                        }
                    }
                    if (!badEdge) {
                        polygon.add(edges.get(j));
                    }
                }
            }
            
            // 从三角网中移除Bad Triangles
            for (int i = badTriangles.size() - 1; i >= 0; i--) {
                triangles.remove(badTriangles.get(i));
            }
            
            // 生成新三角形
            for (int polygonIndex = 0; polygonIndex < polygon.size(); polygonIndex++) {
                Edge edge1 = polygon.get(polygonIndex);
                Point pointA = new Point(point.x, point.y);
                Point pointB = new Point(edge1.p1);
                Point pointC = new Point(edge1.p2);
                triangles.add(new Triangle(pointA, pointB, pointC));
            }
        }

        // 移除包含超级三角形顶点的三角形
        triangles.removeIf(triangle -> triangleContainsPoint(triangle, superPoint1)     
                || triangleContainsPoint(triangle, superPoint2) 
                || triangleContainsPoint(triangle, superPoint3));

        return triangles;
    }

    public Triangle createSuperTriangle() {
        double minX = Double.POSITIVE_INFINITY;
        double minY = Double.POSITIVE_INFINITY;
        double maxX = Double.NEGATIVE_INFINITY;
        double maxY = Double.NEGATIVE_INFINITY;

        for (Point point : points) {
            minX = Math.min(minX, point.x);
            minY = Math.min(minY, point.y);
            maxX = Math.max(maxX, point.x);
            maxY = Math.max(maxY, point.y);
        }

        double deltaX = maxX - minX;
        double deltaY = maxY - minY;
        double deltaMax = Math.max(deltaX, deltaY);

        superPoint1 = new Point(minX - deltaMax, minY - 2 * deltaMax);
        superPoint2 = new Point(minX + 3 * deltaMax, minY - deltaMax);
        superPoint3 = new Point(minX + deltaMax, minY + 3 * deltaMax);

        return new Triangle(superPoint1, superPoint2, superPoint3);
    }

    private boolean triangleContainsPoint(Triangle triangle, Point point) {
        double d1 = sign(point, triangle.p1, triangle.p2);
        double d2 = sign(point, triangle.p2, triangle.p3);
        double d3 = sign(point, triangle.p3, triangle.p1);

        boolean hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
        boolean hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);

        return !(hasNeg && hasPos);
    }

    private double sign(Point p1, Point p2, Point p3) {
        return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
    }
}

class Triangle {
    public Point p1, p2, p3;
    private List<Point> points = new ArrayList<>();
    private Point cachedCircumCentre; // 新增缓存变量

    public Triangle(Point p1, Point p2, Point p3) {
        this.p1 = p1;
        this.p2 = p2;
        this.p3 = p3;
        points.add(p1);
        points.add(p2);
        points.add(p3);
    }

    public boolean ContainsEdge1(Edge edge) {
        int sharedVerts = 0;
        for (int i = 0; i < points.size(); i++) {
            if (points.get(i).equals(edge.p1) || points.get(i).equals(edge.p2)) {
                sharedVerts++;
            }
        }
        return sharedVerts == 2;
    }

    public boolean ContainsEdge(Edge edge) {
        return points.contains(edge.p1) && points.contains(edge.p2);
    }

    public List<Edge> getEdges() {
        List<Edge> edges = new ArrayList<>();
        // 修复:补上第三条边
        edges.add(new Edge(points.get(0), points.get(1)));
        edges.add(new Edge(points.get(1), points.get(2)));
        edges.add(new Edge(points.get(2), points.get(0)));
        return edges;
    }

    public List<Point> getPoints() {
        return points;
    }

    public double circumRadius() {
        Point circumCentre = circumCentre();
        double xDelta = p1.x - circumCentre.x;
        double yDelta = p1.y - circumCentre.y;
        double distance = Math.hypot(xDelta, yDelta);
        return distance;
    }

    public Point circumCentre() {
        if (cachedCircumCentre != null) {
            return cachedCircumCentre;
        }
        double ax = p1.x;
        double ay = p1.y;
        double bx = p2.x;
        double by = p2.y;
        double cx = p3.x;
        double cy = p3.y;

        double d = (ax * (by - cy) + bx * (cy - ay) + cx * (ay - by)) * 2;

        double centerX = (((Math.pow(ax, 2) + Math.pow(ay, 2)) * (by - cy))
                + ((Math.pow(bx, 2) + Math.pow(by, 2)) * (cy - ay)) 
                + (Math.pow(cx, 2) + Math.pow(cy, 2)) * (ay - by)) / d;

        double centerY = (((Math.pow(ax, 2) + Math.pow(ay, 2)) * (cx - bx))
                + ((Math.pow(bx, 2) + Math.pow(by, 2)) * (ax - cx)) 
                + (Math.pow(cx, 2) + Math.pow(cy, 2)) * (bx - ax)) / d;

        cachedCircumCentre = new Point(centerX, centerY);
        return cachedCircumCentre;
    }
}

class Edge {
    public Point p1, p2;
    public Edge(Point p1, Point p2) {
        this.p1 = p1;
        this.p2 = p2;
    }
}

class Point {
    public double x, y;
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    public Point(Point p) {
        this.x = p.x;
        this.y = p.y;
    }
    // 必须重写equals和hashCode,否则contains判断失效
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point point = (Point) o;
        return Double.compare(point.x, x) == 0 && Double.compare(point.y, y) == 0;
    }
    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }
}

解决方案

1. 修复外接圆内点判断的精度问题

原判断使用开方运算(Math.hypot)会损失精度,且未处理浮点数误差。替换为平方比较,并加入epsilon阈值:

// 替换原Bad Triangle识别逻辑中的判断部分
Point centre = triangle.circumCentre();
double dx = point.x - centre.x;
double dy = point.y - centre.y;
double distSq = dx*dx + dy*dy;
double radiusSq = triangle.circumRadius() * triangle.circumRadius();
// 用epsilon处理浮点数精度误差,避免因计算误差导致误判
if (distSq < radiusSq - 1e-8) {
    badTriangles.add(triangle);
}

2. 修复Triangle类的getEdges方法

原方法只生成两条边,导致构建多边形时缺失边,进而引发后续逻辑错误。补上第三条边(见上方代码中Triangle类的getEdges方法)。

3. 确保Point类重写equals和hashCode

如果Point类未重写这两个方法,points.contains(edge.p1)会默认使用引用比较,导致判断失效,进而错误地将所有边加入多边形,最终删除全部三角形。上方代码已补充这两个方法的实现。

4. 优化Bad Triangles的移除逻辑

原移除方式依赖对象引用的一致性,改用集合判断更可靠:

// 替换原移除Bad Triangles的循环
java.util.Set<Triangle> badSet = new java.util.HashSet<>(badTriangles);
triangles.removeIf(badSet::contains);

5. 缓存外接圆圆心计算结果

每次调用circumCentre()都会重新计算,引入额外的计算误差和性能消耗。在Triangle类中加入缓存变量(见上方代码中的cachedCircumCentre),避免重复计算。


内容的提问来源于stack exchange,提问作者steve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 14:37:04