diff --git a/.idea/artifacts/polygon_offset_1_0.xml b/.idea/artifacts/polygon_offset_1_0.xml new file mode 100644 index 0000000..8356f8c --- /dev/null +++ b/.idea/artifacts/polygon_offset_1_0.xml @@ -0,0 +1,8 @@ + + + $PROJECT_DIR$/out/artifacts/polygon_offset_1_0 + + + + + \ No newline at end of file diff --git a/.idea/dictionaries b/.idea/dictionaries new file mode 100644 index 0000000..74fa4f7 --- /dev/null +++ b/.idea/dictionaries @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/free-mybatis-generator-config.xml b/.idea/free-mybatis-generator-config.xml new file mode 100644 index 0000000..55d51f3 --- /dev/null +++ b/.idea/free-mybatis-generator-config.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/.idea/google-java-format.xml b/.idea/google-java-format.xml new file mode 100644 index 0000000..05946a8 --- /dev/null +++ b/.idea/google-java-format.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..115d117 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5ab29ca --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml new file mode 100644 index 0000000..e96534f --- /dev/null +++ b/.idea/uiDesigner.xml @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..85a8db9 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,216 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1614925542991 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..559acf6 --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +[toc] + +## 一、项目介绍 + + + +## 二、算法步骤 + + + +## 三、结果演示 + + + +## 四、不足之处 + + + +## 五、参考资料 + diff --git a/file/5edge.txt b/file/5edge.txt new file mode 100644 index 0000000..878c2af --- /dev/null +++ b/file/5edge.txt @@ -0,0 +1,5 @@ +100,100 +300,100 +300,300 +200,300 +100,400 \ No newline at end of file diff --git a/file/rectangle.txt b/file/rectangle.txt new file mode 100644 index 0000000..56b4fb4 --- /dev/null +++ b/file/rectangle.txt @@ -0,0 +1,4 @@ +100,100 +200,100 +200,200 +100,200 \ No newline at end of file diff --git a/file/star.txt b/file/star.txt new file mode 100644 index 0000000..f18b13b --- /dev/null +++ b/file/star.txt @@ -0,0 +1,8 @@ +50,200 +200,250 +250,300 +300,250 +450,200 +300,150 +250,100 +200,150 \ No newline at end of file diff --git a/file/triangle.txt b/file/triangle.txt new file mode 100644 index 0000000..46e65c0 --- /dev/null +++ b/file/triangle.txt @@ -0,0 +1,3 @@ +100,100 +100,300 +200,400 \ No newline at end of file diff --git a/file/tunnel.txt b/file/tunnel.txt new file mode 100644 index 0000000..e775c6a --- /dev/null +++ b/file/tunnel.txt @@ -0,0 +1,14 @@ +100,100 +170,150 +240,150 +300,100 +360,150 +430,150 +500,100 +500,300 +430,250 +360,250 +300,300 +240,250 +170,250 +100,300 \ No newline at end of file diff --git a/out/artifacts/polygon_offset_1_0/polygon-offset-1.0.jar b/out/artifacts/polygon_offset_1_0/polygon-offset-1.0.jar new file mode 100644 index 0000000..667c5ab Binary files /dev/null and b/out/artifacts/polygon_offset_1_0/polygon-offset-1.0.jar differ diff --git a/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm$1.class b/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm$1.class new file mode 100644 index 0000000..20f0bc5 Binary files /dev/null and b/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm$1.class differ diff --git a/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm.class b/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm.class new file mode 100644 index 0000000..16465a3 Binary files /dev/null and b/out/production/polygon_offset/com/lee/algorithm/OffsetAlgorithm.class differ diff --git a/out/production/polygon_offset/com/lee/display/DrawingBoard.class b/out/production/polygon_offset/com/lee/display/DrawingBoard.class new file mode 100644 index 0000000..3870c02 Binary files /dev/null and b/out/production/polygon_offset/com/lee/display/DrawingBoard.class differ diff --git a/out/production/polygon_offset/com/lee/display/MainWindow.class b/out/production/polygon_offset/com/lee/display/MainWindow.class new file mode 100644 index 0000000..0524963 Binary files /dev/null and b/out/production/polygon_offset/com/lee/display/MainWindow.class differ diff --git a/out/production/polygon_offset/com/lee/entity/LineSegment.class b/out/production/polygon_offset/com/lee/entity/LineSegment.class new file mode 100644 index 0000000..1b8129a Binary files /dev/null and b/out/production/polygon_offset/com/lee/entity/LineSegment.class differ diff --git a/out/production/polygon_offset/com/lee/entity/Point.class b/out/production/polygon_offset/com/lee/entity/Point.class new file mode 100644 index 0000000..c68e722 Binary files /dev/null and b/out/production/polygon_offset/com/lee/entity/Point.class differ diff --git a/out/production/polygon_offset/com/lee/entity/PointLink.class b/out/production/polygon_offset/com/lee/entity/PointLink.class new file mode 100644 index 0000000..fa29c6e Binary files /dev/null and b/out/production/polygon_offset/com/lee/entity/PointLink.class differ diff --git a/out/production/polygon_offset/com/lee/entity/Vector.class b/out/production/polygon_offset/com/lee/entity/Vector.class new file mode 100644 index 0000000..790ddb3 Binary files /dev/null and b/out/production/polygon_offset/com/lee/entity/Vector.class differ diff --git a/out/production/polygon_offset/com/lee/util/ArithUtil.class b/out/production/polygon_offset/com/lee/util/ArithUtil.class new file mode 100644 index 0000000..0593481 Binary files /dev/null and b/out/production/polygon_offset/com/lee/util/ArithUtil.class differ diff --git a/out/production/polygon_offset/com/lee/util/MathUtil.class b/out/production/polygon_offset/com/lee/util/MathUtil.class new file mode 100644 index 0000000..158de76 Binary files /dev/null and b/out/production/polygon_offset/com/lee/util/MathUtil.class differ diff --git a/out/production/polygon_offset/com/lee/util/OffsetUtil.class b/out/production/polygon_offset/com/lee/util/OffsetUtil.class new file mode 100644 index 0000000..4b0a89d Binary files /dev/null and b/out/production/polygon_offset/com/lee/util/OffsetUtil.class differ diff --git a/out/test/polygon_offset/com/lee/test/OffsetTest.class b/out/test/polygon_offset/com/lee/test/OffsetTest.class new file mode 100644 index 0000000..fdeacf0 Binary files /dev/null and b/out/test/polygon_offset/com/lee/test/OffsetTest.class differ diff --git a/out/test/polygon_offset/com/lee/util/FileUtil.class b/out/test/polygon_offset/com/lee/util/FileUtil.class new file mode 100644 index 0000000..bee0e4b Binary files /dev/null and b/out/test/polygon_offset/com/lee/util/FileUtil.class differ diff --git a/polygon_offset.iml b/polygon_offset.iml new file mode 100644 index 0000000..6ccdc21 --- /dev/null +++ b/polygon_offset.iml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/com/lee/algorithm/OffsetAlgorithm.java b/src/com/lee/algorithm/OffsetAlgorithm.java new file mode 100644 index 0000000..da9c76d --- /dev/null +++ b/src/com/lee/algorithm/OffsetAlgorithm.java @@ -0,0 +1,646 @@ +package com.lee.algorithm; + +import com.lee.entity.LineSegment; +import com.lee.entity.Point; +import com.lee.entity.PointLink; +import com.lee.entity.Vector; +import com.lee.util.ArithUtil; +import com.lee.util.MathUtil; +import com.lee.util.OffsetUtil; + +import java.util.*; + +public class OffsetAlgorithm { + + /** + * 生成缩进轮廓算法 + * @param points 闭合轮廓 + * @param distance 缩进距离,如果distance>0,向轮廓里缩进;如果distance<0,外扩 + * @return + */ + public static List> offsetAlgorithm(List points, double distance){ + if (points == null || points.size() < 3 || Double.compare(distance,0.0) == 0){ + return null; + } + + // 将points中的重复点删除,保持顺序 + LinkedHashSet linkedHashSet1 = new LinkedHashSet<>(points); + points = new ArrayList<>(linkedHashSet1); + + // 计算缩进方向 + Map> direction = getDirection(points); + List in = direction.get("in"); + + // 确定内缩点 + List inShell = getInfillPoints(points, in, distance); + + // 确定每条缩进线段的有效性 + List validOffsetLine = isValidOffsetLine(points, inShell); + + // 是否访问 + boolean[] isVisited = new boolean[validOffsetLine.size()]; + Arrays.fill(isVisited, false); + + // 无效内缩线段的处理 + List finalInshell = new ArrayList<>(); + for (int i = 0; i < inShell.size(); ) { + if (validOffsetLine.get(i)) { + // 以i点开头的线段是有效边 + finalInshell.add(inShell.get(i)); + isVisited[i] = true; + i++; + } else { + // 以i点开头的线段是无效边 + if (isVisited[i]) { + // 访问过了,不再处理 + continue; + } + // 确定case1 或 case2 + int backIndex = (i - 1 + validOffsetLine.size()) % validOffsetLine.size(); + while (!validOffsetLine.get(backIndex)) { + backIndex = + (backIndex - 1 + validOffsetLine.size()) % validOffsetLine.size(); + if (backIndex == i) { +// System.out.println("backward:all invalid offset lines"); + break; + } + } + int current = i; + int next = (i + 1) % validOffsetLine.size(); + while (!validOffsetLine.get(next)) { + next = (next + 1) % validOffsetLine.size(); + if (next == i) { +// System.out.println("forward:all invalid offset lines"); + break; + } + } +// System.out.println("back = " + backIndex + ", i = " + i + ", next = " + next); + // 根据case 1 和case 2寻找交点 + if (next == backIndex) { + // all edges are invalid edges + return null; +// break; + } else if (next - backIndex == 2 + || (next == 1 && backIndex == validOffsetLine.size() - 1) + || (next == 0 && backIndex == validOffsetLine.size() - 2)) { + // case 1 只有一条无效边 + // 1. 计算backward edge 和forward edge的交点 + double[] backwardEdge = + MathUtil.getLineFromTwoPoints( + inShell.get(backIndex), inShell.get(current)); + double[] forwardEdge = + MathUtil.getLineFromTwoPoints( + inShell.get(next), + inShell.get((next + 1) % validOffsetLine.size())); + + Point intersection = + MathUtil.getIntersectionOfTwoLines(backwardEdge, forwardEdge); +// System.out.println("intersection=" + intersection); + // 2. 更新数据 + if (next == 0) { + finalInshell.set(next, intersection); + } else { + inShell.set(next, intersection); + } + isVisited[i] = true; + i++; + } else { + // case 2: 有连续的无效边 + LineSegment backwardEdge = + new LineSegment(inShell.get(backIndex), inShell.get(current)); + LineSegment forwardEdge = + new LineSegment( + inShell.get(next), + inShell.get((next + 1) % inShell.size())); + // 计算backward和forward与轮廓是否有交点 + + for (int j = current; j != next; j = (j + 1) % validOffsetLine.size()) { + isVisited[j] = true; + LineSegment boundaryEdge = + new LineSegment( + points.get(j), points.get((j + 1) % points.size())); +// System.out.println("j=" + j + ",j_next=" + (j + 1) % points.size()); +// System.out.println("boundary edge=" + boundaryEdge); + + // 计算boundary的pair-wise offset + // 1. 偏移方向 + com.lee.entity.Vector normalVector = getNormalVector(boundaryEdge); + if (Double.compare(MathUtil.getMulOfVector(normalVector, in.get(j)), 0) + < 0) { + normalVector.mul(-1); + } + normalVector.normalize(); + normalVector.mul(3 * distance); +// System.out.println("偏移方向=" + normalVector); + // 2. 计算偏移线段 + Point first = + new Point( + boundaryEdge.getStartPoint().getX() + normalVector.getX(), + boundaryEdge.getStartPoint().getY() + normalVector.getY()); + Point second = + new Point( + boundaryEdge.getEndPoint().getX() + normalVector.getX(), + boundaryEdge.getEndPoint().getY() + normalVector.getY()); + LineSegment offsetEdgeOfPairwise = new LineSegment(first, second); + // drawLineSegment(offsetEdgeOfPairwise,g); + // 计算偏移线段与backward、forward的交点,并更新 + // 此处应该计算偏移线段offsetEdgeOfPairwise 与所有有效偏移边的交点 + // 不只是backward 和 forward + // + // System.out.println("offsetEdge="+offsetEdgeOfPairwise+", + // backwardEdge="+backwardEdge); + + Point point2 = new OffsetUtil().intersection(offsetEdgeOfPairwise, backwardEdge); +// Point point2 = +// MathUtil.intersectionOfTwoLineSegment( +// offsetEdgeOfPairwise, backwardEdge); + // System.out.println(point2); + if (point2 != null) { + backwardEdge.setEndPoint(point2); + } + + // System.out.println("offsetEdge="+offsetEdgeOfPairwise+", + // forwardEdge="+forwardEdge); + Point point1 =new OffsetUtil().intersection(offsetEdgeOfPairwise, forwardEdge); + // System.out.println(point1); + if (point1 != null) { + forwardEdge.setStartPoint(point1); + } + } + // 所有的无效边处理完成后 + // 计算backward 和forward的交点 +// System.out.println( +// "backwardEdge=" + backwardEdge + ",forwardEdge=" + forwardEdge); + Point point =new OffsetUtil().intersection(backwardEdge, forwardEdge); + Point preForwardStartPoint = forwardEdge.getStartPoint(); + if (point != null) { + backwardEdge.setEndPoint(point); + forwardEdge.setStartPoint(point); + } + + finalInshell.add(backwardEdge.getEndPoint()); + + if (next < current) { + i = inShell.size(); + if (point != null) { + for (int k = 0; k < finalInshell.size(); k++){ + if (finalInshell.get(k).equals(preForwardStartPoint)){ + finalInshell.set(k, point); + break; + } + } + } + } else { + inShell.set(next, forwardEdge.getStartPoint()); + i = next; + } + } + } + } + + // 全局无效环的处理 + if (finalInshell.size() >= 3 ) { + // 将finalInshell中处于轮廓外的点消除 + List finalFinalInshell = new ArrayList<>(); + List contour = new ArrayList<>(); + for (int m = 0; m < points.size(); m++){ + LineSegment lineSegment = new LineSegment(points.get(m), points.get((m + 1) % points.size())); + contour.add(lineSegment); + } + + for (Point point: finalInshell){ + if (point == null){ + continue; + } + if (Double.compare(distance, 0.0) > 0) { + // 向内缩进 + if (MathUtil.isInPolygon(contour, point)) { + finalFinalInshell.add(point); + } + }else if (Double.compare(distance,0.0) < 0){ + // 向外扩张 + if (!MathUtil.isInPolygon(contour, point)) { + finalFinalInshell.add(point); + } + } + } + finalInshell = finalFinalInshell; + // finalInshell 可能有相同的点,去除重复点 + LinkedHashSet linkedHashSet = new LinkedHashSet<>(finalInshell); + finalInshell = new ArrayList<>(linkedHashSet); + + // 消除全局无效环 + // 1.获取所有的自交点 + Map> intersection = getIntersection(finalInshell); + + if (intersection.size() == 0) { + // 如果没有自交点,说明不存在全局无效环,不用处理 + List> res = new ArrayList<>(); + res.add(finalInshell); + return res; + } else { + // 有自交点,需要消除全局无效环 + List allPoints = new ArrayList<>(); + List isIntersectionPoint = new ArrayList<>(); + for (int i = 0; i < finalInshell.size(); i++) { + allPoints.add(finalInshell.get(i)); + isIntersectionPoint.add(false); + if (intersection.get(finalInshell.get(i)) != null) { + for (int j = 0; j < intersection.get(finalInshell.get(i)).size(); j++) { + allPoints.add(intersection.get(finalInshell.get(i)).get(j)); + isIntersectionPoint.add(true); + } + } + } + + List> lists = + processGlobalInvalidLoop(points, allPoints, isIntersectionPoint); + return lists; + } + } + return null; + } + + /** + * 消除全局无效环 + * + * @param contour 原轮廓 + * @param allPoints 包含自交点的缩进轮廓 + * @param isIntersectionPoint 判断allPoints是否为自交点 + * @return + */ + private static List> processGlobalInvalidLoop( + List contour, List allPoints, List isIntersectionPoint) { + // 判断原轮廓的方向 + boolean isClockwiseOfContour = isClockwise(contour); + // 判断该点是否被访问过 + boolean[] isVisited = new boolean[allPoints.size()]; + Arrays.fill(isVisited, false); + // 任选一个起始点 + Point start = null; + int indexOfStartPoint = -1; + for (int i = 0; i < allPoints.size(); i++) { + if (!isIntersectionPoint.get(i)) { + start = allPoints.get(i); + indexOfStartPoint = i; + break; + } + } + + Stack stack = new Stack<>(); + Stack indexOfStartPointStack = new Stack<>(); + stack.push(start); + indexOfStartPointStack.push(indexOfStartPoint); + + List> res = new ArrayList<>(); + + while (!stack.isEmpty()) { + Point startPoint = stack.pop(); + int index = indexOfStartPointStack.pop(); + isVisited[index] = true; + + List loop = new ArrayList<>(); + loop.add(startPoint); + + Point nextPoint = allPoints.get((index + 1) % allPoints.size()); + int indexOfNextPoint = (index + 1) % allPoints.size(); + while (!nextPoint.equals(startPoint)) { + if (isVisited[indexOfNextPoint]) { + // 重复访问某点,则表示生成的轮廓有问题,直接返回null + return null; + } else { + if (isIntersectionPoint.get(indexOfNextPoint) ) { + // nextPoint是自相交的点 + stack.push(nextPoint); + indexOfStartPointStack.push(indexOfNextPoint); + // 切换到另一个方向 + for (int k = 0; k < allPoints.size(); k++) { + if (nextPoint.equals(allPoints.get(k))) { + if (k != indexOfNextPoint) { + isVisited[k] = true; + loop.add(allPoints.get(k)); + nextPoint = allPoints.get((k + 1) % allPoints.size()); + indexOfNextPoint = (k + 1) % allPoints.size(); + break; + } + } + } + } else { + // nextPoint 不是自相交的点 + loop.add(nextPoint); + isVisited[indexOfNextPoint] = true; + nextPoint = allPoints.get((indexOfNextPoint + 1) % allPoints.size()); + indexOfNextPoint = (indexOfNextPoint + 1) % allPoints.size(); + } + } + } + + if (isClockwise(loop) == isClockwiseOfContour && loop.size() >= 3) { + res.add(loop); + } + } + return res; + } + + /** + * 获取所有的自交点 + * + * @param points + * @return + */ + private static Map> getIntersection(List points) { + Map> intersections = new HashMap<>(); + + for (int i = 0; i < points.size(); i++) { + LineSegment lineSegment = + new LineSegment(points.get(i), points.get((i + 1) % points.size())); + + for (int j = 0; j < points.size(); j++) { + if (Math.abs(i-j) > 1) { + LineSegment a = + new LineSegment(points.get(j), points.get((j + 1) % points.size())); + OffsetUtil offsetUtil = new OffsetUtil(); + Point point = null; + if (i < j ){ + point = offsetUtil.intersection(lineSegment, a); + }else { + point = offsetUtil.intersection(a,lineSegment); + } + + if (point != null) { + if (!point.equals(lineSegment.getStartPoint()) + && !point.equals(lineSegment.getEndPoint()) + && !point.equals(a.getStartPoint()) + && !point.equals(a.getEndPoint())) { + + if (intersections.get(points.get(i)) != null){ + intersections.get(points.get(i)).add(point); + }else { + List intersection = new ArrayList<>(); + intersection.add(point); + intersections.put(points.get(i), intersection); + } + + if (intersections.get(points.get(j)) != null){ + intersections.get(points.get(j)).add(point); + }else { + List intersection = new ArrayList<>(); + intersection.add(point); + intersections.put(points.get(j), intersection); + } + } + } + } + } + } + + // 去除重复的点 + for (Point point:intersections.keySet()){ + LinkedHashSet linkedHashSet = new LinkedHashSet<>(intersections.get(point)); + intersections.put(point, new ArrayList<>(linkedHashSet)); + } + // 自交点排序 + for (Point point : intersections.keySet()) { + List pointList = intersections.get(point); + Collections.sort( + pointList, + new Comparator() { + @Override + public int compare(Point o1, Point o2) { + double distance1 = MathUtil.getDistanceBetweenTwoPoints(point, o1); + double distance2 = MathUtil.getDistanceBetweenTwoPoints(point, o2); + return Double.compare(distance1, distance2); + } + }); + } + + return intersections; + } + + private static PointLink toPointLink(List points) { + List pointLinks = new ArrayList<>(); + + for (int i = 0; i < points.size(); i++) { + PointLink pointLink = new PointLink(); + pointLink.setPoint(points.get(i)); + pointLink.setIntersection(false); + pointLinks.add(pointLink); + } + + for (int i = 0; i < pointLinks.size(); i++) { + pointLinks.get(i).setNext(pointLinks.get((i + 1) % pointLinks.size())); + } + + return pointLinks.get(0); + } + + /** + * 判断多边形是否为顺时针方向 + * https://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order + * + * @param contour + * @return true if contour is in clockwise + */ + private static boolean isClockwise(List contour) { + double sum = 0; + for (int i = 0; i < contour.size(); i++) { + int next = (i + 1) % contour.size(); + + double x1 = contour.get(i).getX(); + double y1 = contour.get(i).getY(); + double x2 = contour.get(next).getX(); + double y2 = contour.get(next).getY(); + + sum += (x2 - x1) * (y1 + y2); + } + + if (Double.compare(sum, 0) > 0) { + return true; + } + return false; + } + + /** + * 获取一条线段的法线方向 + * + * @param lineSegment + * @return + */ + private static com.lee.entity.Vector getNormalVector(LineSegment lineSegment) { + double[] line = MathUtil.getLineFromLineSegment(lineSegment); + com.lee.entity.Vector vector = new com.lee.entity.Vector(); + vector.setX(line[0]); + vector.setY(line[1]); + return vector; + } + + /** + * 确定缩进轮廓线是否有效 + * + * @param points 原轮廓线中的点 + * @param offset 缩进点 + * @return res.get(i)表示 (i,i+1)线是否有效,true表示有效 + */ + private static List isValidOffsetLine(List points, List offset) { + List res = new ArrayList<>(); + for (int i = 0; i < points.size(); i++) { + int next = (i + 1) % points.size(); + com.lee.entity.Vector originalVector = + MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(next)); + com.lee.entity.Vector offsetVector = + MathUtil.getNormalVectorFromTwoPoints(offset.get(i), offset.get(next)); + double mul = MathUtil.getMulOfVector(originalVector, offsetVector); + if (Double.compare(mul, 0) == -1) { + res.add(false); + } else { + res.add(true); + } + } + + return res; + } + + /** + * 获取轮廓的内缩点 + * + * @param points + * @param in + * @param distance + * @return + */ + private static List getInfillPoints( + List points, List in, double distance) { + List inPoints = new ArrayList<>(); + for (int i = 0; i < points.size(); i++) { + inPoints.add( + new Point( + points.get(i).getX() + in.get(i).getX() * distance, + points.get(i).getY() + in.get(i).getY() * distance)); + } + return inPoints; + } + + /** + * 获取轮廓的外扩点 + * + * @param points 原轮廓点 + * @param out 外扩方向 + * @param distance 外扩距离 + * @return 外扩点 + */ + private static List getExtendPoints( + List points, List out, double distance) { + List outPoints = new ArrayList<>(); + for (int i = 0; i < points.size(); i++) { + outPoints.add( + new Point( + points.get(i).getX() + out.get(i).getX() * distance, + points.get(i).getY() + out.get(i).getY() * distance)); + } + return outPoints; + } + + /** + * 确定一个轮廓向内和向外的缩进方向 + * + * @param points + * @return map.get("in") 向内缩进的方向,map.get("out") 外扩的方向 + */ + private static Map> getDirection(List points) { + Map> map = new HashMap<>(); + + List in = new ArrayList<>(); + List out = new ArrayList<>(); + + for (int i = 0; i < points.size(); i++) { + + com.lee.entity.Vector vector1; + com.lee.entity.Vector vector2; + if (i == 0) { + vector1 = + MathUtil.getNormalVectorFromTwoPoints( + points.get(i), points.get(points.size() - 1)); + vector2 = MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(i + 1)); + } else if (i == points.size() - 1) { + vector1 = MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(i - 1)); + vector2 = MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(0)); + } else { + vector1 = MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(i - 1)); + vector2 = MathUtil.getNormalVectorFromTwoPoints(points.get(i), points.get(i + 1)); + } + // 计算缩进方向 + com.lee.entity.Vector vector = new com.lee.entity.Vector(); + vector.setX(ArithUtil.add(vector1.getX(), vector2.getX())); + vector.setY(ArithUtil.add(vector1.getY(), vector2.getY())); + + if (Double.compare(0, vector.getLength()) == 0) { + if (Double.compare(vector1.getX(), 0) == 0 + && Double.compare(vector2.getX(), 0) == 0) { + vector.setX(1.0); + vector.setY(0.0); + } else if (Double.compare(vector1.getY(), 0) == 0 + && Double.compare(vector2.getY(), 0) == 0) { + vector.setX(0.0); + vector.setY(1.0); + } else { + vector.setX(points.get(i).getX()); + double y = + ArithUtil.mul( + vector.getX(), + ArithUtil.div( + -1, ArithUtil.div(vector1.getY(), vector1.getX()))); + vector.setY(y); + } + } + // 计算偏移距离 + double cos = + ArithUtil.div( + MathUtil.getMulOfVector(vector1, vector2), + ArithUtil.mul(vector1.getLength(), vector2.getLength())); + // 半角公式 + double sin2 = Math.sqrt((1 - cos) / 2); + double L = 1.0; + if (Double.compare(0.1, sin2) <= 0) { + L = 1.0 / sin2; + } + vector.normalize(); + vector.mul(L); + + // 结果点 + Point point = + new Point( + ArithUtil.add(points.get(i).getX(), vector.getX()), + ArithUtil.add(points.get(i).getY(), vector.getY())); + // 判断结果点是在多边形外面还是里面,射线法 + List contour = new ArrayList<>(); + for (int m = 0; m < points.size(); m++){ + LineSegment lineSegment = new LineSegment(points.get(m), points.get((m + 1) % points.size())); + contour.add(lineSegment); + } + + boolean inPolygon = MathUtil.isInPolygon(contour, point); + com.lee.entity.Vector pointToIn = null; + com.lee.entity.Vector pointToOut = null; + if (inPolygon) { + // 在轮廓里,说明vector是指向轮廓里的方向 + pointToIn = vector; + pointToOut = new com.lee.entity.Vector(); + pointToOut.setX(ArithUtil.mul(vector.getX(), -1)); + pointToOut.setY(ArithUtil.mul(vector.getY(), -1)); + } else { + pointToOut = vector; + pointToIn = new Vector(); + pointToIn.setX(ArithUtil.mul(vector.getX(), -1)); + pointToIn.setY(ArithUtil.mul(vector.getY(), -1)); + } + + in.add(pointToIn); + out.add(pointToOut); + } + + map.put("in", in); + map.put("out", out); + + return map; + } +} diff --git a/src/com/lee/display/DrawingBoard.java b/src/com/lee/display/DrawingBoard.java new file mode 100644 index 0000000..fcca746 --- /dev/null +++ b/src/com/lee/display/DrawingBoard.java @@ -0,0 +1,48 @@ +package com.lee.display; + +import com.lee.entity.Point; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class DrawingBoard extends JPanel { + + private List> contours ; + + public DrawingBoard(){ + this.contours = null; + } + + public DrawingBoard(List> contours){ + this.contours = contours; + } + + public List> getContours() { + return contours; + } + + public void setContours(List> contours) { + this.contours = contours; + } + + /** + * 画图主要函数 + * @param g + */ + @Override + public void paint(Graphics g) { + super.paint(g); + for (List contour:this.contours){ + drawContour(contour,g); + } + } + + public void drawContour(List contour,Graphics g){ + for (int i = 0; i < contour.size(); i++){ + int next = (i+1)%contour.size(); + g.drawLine((int)contour.get(i).getX(),(int)contour.get(i).getY(), + (int)contour.get(next).getX(),(int)contour.get(next).getY()); + } + } +} diff --git a/src/com/lee/display/MainWindow.java b/src/com/lee/display/MainWindow.java new file mode 100644 index 0000000..f95334e --- /dev/null +++ b/src/com/lee/display/MainWindow.java @@ -0,0 +1,35 @@ +package com.lee.display; + +import com.lee.entity.Point; + +import javax.swing.*; +import java.awt.*; +import java.util.List; + +public class MainWindow { + private JFrame frame; + private DrawingBoard drawingBoard; + + public MainWindow(List> contours){ + frame = new JFrame("polygon offset algorithm display"); + frame.setSize(600, 600); + frame.setLocationRelativeTo(null); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); + + drawingBoard = new DrawingBoard(contours); + drawingBoard.setBackground(Color.WHITE); + frame.add(drawingBoard); + } + + public DrawingBoard getDrawingBoard() { + return drawingBoard; + } + + public void setDrawingBoard(DrawingBoard drawingBoard) { + this.drawingBoard = drawingBoard; + } + + public void show(){ + frame.setVisible(true); + } +} diff --git a/src/com/lee/entity/LineSegment.java b/src/com/lee/entity/LineSegment.java new file mode 100644 index 0000000..412a7fb --- /dev/null +++ b/src/com/lee/entity/LineSegment.java @@ -0,0 +1,72 @@ +package com.lee.entity; + +import java.io.Serializable; +import java.util.Objects; + +/** 轮廓上的线段 */ +public class LineSegment implements Serializable { + private Point startPoint; // 第一个点 + private Point endPoint; // 第二个点 + private LineSegment next; // 其连接的下一条线段,即next边的一个点与当前线段的一个点相同 + + public LineSegment(Point startPoint, Point endPoint){ + this.startPoint = startPoint; + this.endPoint = endPoint; + this.next = null; + } + + public LineSegment getNext() { + return next; + } + + public void setNext(LineSegment next) { + this.next = next; + } + + public Point getStartPoint() { + return startPoint; + } + + public void setStartPoint(Point startPoint) { + this.startPoint = startPoint; + } + + public Point getEndPoint() { + return endPoint; + } + + public void setEndPoint(Point endPoint) { + this.endPoint = endPoint; + } + + /** + * point 是否为该线段的端点 + * @param point + * @return + */ + public boolean isEndPoint(Point point){ + return this.startPoint.equals(point) || this.endPoint.equals(point); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LineSegment that = (LineSegment) o; + return (Objects.equals(startPoint, that.startPoint) && Objects.equals(endPoint, that.endPoint))|| + (Objects.equals(startPoint, that.endPoint) && Objects.equals(endPoint, that.startPoint)); + } + + @Override + public int hashCode() { + return Objects.hash(startPoint, endPoint); + } + + @Override + public String toString() { + return "LineSegment{" + + "startPoint=" + startPoint + + ", endPoint=" + endPoint + + '}'; + } +} diff --git a/src/com/lee/entity/Point.java b/src/com/lee/entity/Point.java new file mode 100644 index 0000000..d2df9f5 --- /dev/null +++ b/src/com/lee/entity/Point.java @@ -0,0 +1,62 @@ +package com.lee.entity; + +import java.io.Serializable; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Objects; + +/** + * 点 + */ +public class Point implements Serializable { + double x; + double y; + + public Point(double x,double y){ + BigDecimal bigDecimalx = new BigDecimal(x); + this.x = bigDecimalx.setScale(6, RoundingMode.HALF_UP).doubleValue(); + BigDecimal bigDecimaly = new BigDecimal(y); + this.y = bigDecimaly.setScale(6, RoundingMode.HALF_UP).doubleValue(); + } + + public double getX() { + return x; + } + + public void setX(double x) { + BigDecimal bigDecimalx = new BigDecimal(x); + this.x = bigDecimalx.setScale(6, RoundingMode.HALF_UP).doubleValue(); + } + + public double getY() { + return y; + } + + public void setY(double y) { + BigDecimal bigDecimaly = new BigDecimal(y); + this.y = bigDecimaly.setScale(6, RoundingMode.HALF_UP).doubleValue(); + } + + @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; + return Double.compare(Math.abs(point.x-x) , 1e-5)<0 && Double.compare(Math.abs(point.y-y),1e-5)<0; + } + + @Override + public int hashCode() { + return Objects.hash(x, y); + } + + @Override + public String toString() { + return "Point{" + + "x=" + x + + ", y=" + y + + '}'; + } +} diff --git a/src/com/lee/entity/PointLink.java b/src/com/lee/entity/PointLink.java new file mode 100644 index 0000000..7e4bde0 --- /dev/null +++ b/src/com/lee/entity/PointLink.java @@ -0,0 +1,40 @@ +package com.lee.entity; + +public class PointLink { + private Point point; + private Boolean intersection; + private PointLink next; + + public PointLink() { + } + + public PointLink(Point point, PointLink next, Boolean intersection) { + this.point = point; + this.next = next; + this.intersection = intersection; + } + + public Point getPoint() { + return point; + } + + public void setPoint(Point point) { + this.point = point; + } + + public PointLink getNext() { + return next; + } + + public void setNext(PointLink next) { + this.next = next; + } + + public Boolean getIntersection() { + return intersection; + } + + public void setIntersection(Boolean intersection) { + this.intersection = intersection; + } +} diff --git a/src/com/lee/entity/Vector.java b/src/com/lee/entity/Vector.java new file mode 100644 index 0000000..d54f913 --- /dev/null +++ b/src/com/lee/entity/Vector.java @@ -0,0 +1,81 @@ +package com.lee.entity; + +import com.lee.util.ArithUtil; + +/** + * 向量 + */ +public class Vector { + private double x; + private double y; + + public Vector() { + } + + public Vector(double x, double y) { + this.x = x; + this.y = y; + if (Double.compare(Math.abs(this.x),1e-10)<=0){ + this.x = 0; + } + if (Double.compare(Math.abs(this.y),1e-10)<=0){ + this.y = 0; + } + } + + public double getX() { + return x; + } + + public void setX(double x) { + this.x = x; + if (Double.compare(Math.abs(this.x),1e-10)<=0){ + this.x = 0; + } + } + + public double getY() { + return y; + } + + public void setY(double y) { + this.y = y; + if (Double.compare(Math.abs(this.y),1e-10)<=0){ + this.y = 0; + } + } + + /** + * 获取向量的长度 + * @return + */ + public double getLength(){ + return Math.sqrt(ArithUtil.add(Math.pow(x,2), Math.pow(y,2))); + } + + /** + * 向量归一化 + */ + public void normalize(){ + double length = this.getLength(); + this.x = ArithUtil.div(this.x,length); + this.y = ArithUtil.div(this.y,length); + } + + /** + * 向量乘以一个常数t + * @param t + */ + public void mul(double t){ + this.x = ArithUtil.mul(this.x,t); + this.y = ArithUtil.mul(this.y,t); + } + + @Override + public String toString() { + return "Vector{" + + "x=" + x + + ", y=" + y + + '}'; + } +} diff --git a/src/com/lee/util/ArithUtil.java b/src/com/lee/util/ArithUtil.java new file mode 100644 index 0000000..b67efc4 --- /dev/null +++ b/src/com/lee/util/ArithUtil.java @@ -0,0 +1,81 @@ +package com.lee.util; + +import java.math.BigDecimal; +import java.math.RoundingMode; + +/** + * 浮点数精确计算 工具类 + */ +public class ArithUtil { + // 除法运算默认精度 + private static final int DEF_DIV_SCALE = 15; + + /** + * 精确加法 + */ + public static double add(double value1, double value2) { + BigDecimal b1 = BigDecimal.valueOf(value1); + BigDecimal b2 = BigDecimal.valueOf(value2); + return b1.add(b2).doubleValue(); + } + + /** + * 精确减法 + */ + public static double sub(double value1, double value2) { + BigDecimal b1 = BigDecimal.valueOf(value1); + BigDecimal b2 = BigDecimal.valueOf(value2); + return b1.subtract(b2).doubleValue(); + } + + /** + * 精确乘法 + */ + public static double mul(double value1, double value2) { + BigDecimal b1 = BigDecimal.valueOf(value1); + BigDecimal b2 = BigDecimal.valueOf(value2); + return b1.multiply(b2).doubleValue(); + } + + /** + * 精确除法 使用默认精度 + */ + public static double div(double value1, double value2){ + double res = 0.0; + try { + res = div(value1, value2, DEF_DIV_SCALE); + }catch (IllegalAccessException e){ + e.printStackTrace(); + } + return res; + } + + /** + * 精确除法 + * @param scale 精度 + */ + private static double div(double value1, double value2, int scale) throws IllegalAccessException { + if(scale < 0) { + throw new IllegalAccessException("精确度不能小于0"); + } + BigDecimal b1 = BigDecimal.valueOf(value1); + BigDecimal b2 = BigDecimal.valueOf(value2); + // return b1.divide(b2, scale).doubleValue(); + return b1.divide(b2, scale, RoundingMode.HALF_UP).doubleValue(); + } + + /** + * 四舍五入 + * @param scale 小数点后保留几位 + */ + public static double round(double v, int scale) { + double res = 0.0; + try { + res = div(v, 1, scale); + }catch (IllegalAccessException e){ + e.printStackTrace(); + } + return res; + } + +} diff --git a/src/com/lee/util/MathUtil.java b/src/com/lee/util/MathUtil.java new file mode 100644 index 0000000..a52c315 --- /dev/null +++ b/src/com/lee/util/MathUtil.java @@ -0,0 +1,191 @@ +package com.lee.util; + +import com.lee.entity.LineSegment; +import com.lee.entity.Point; +import com.lee.entity.Vector; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + + +/** 扩展的数学工具类 */ +public class MathUtil { + + /** + * 从线段中获取该线段所在的直线 + * @param lineSegment + * @return + */ + public static double[] getLineFromLineSegment(LineSegment lineSegment){ + double[] line = new double[3]; + Point first = lineSegment.getStartPoint(); + Point second = lineSegment.getEndPoint(); + if (Double.compare(first.getX(),second.getX()) == 0){ + // 该线段垂直于x轴 + line[0] = 1.0; + line[1] = 0.0; + line[2] = ArithUtil.mul(-1,first.getX()); + }else if (Double.compare(first.getY(),second.getY()) == 0){ + // 该线段垂直于y轴 + line[0] = 0.0; + line[1] = 1.0; + line[2] = ArithUtil.mul(-1,first.getY()); + }else { + double k = ArithUtil.div(ArithUtil.sub(second.getY(),first.getY()), ArithUtil.sub(second.getX(),first.getX())); + line[0] = k; + line[1] = -1; + line[2] = ArithUtil.sub(first.getY() , ArithUtil.mul(first.getX(),k)); + } + + return line; + } + + /** + * 获取两条直线的交点 + * @param line1 + * @param line2 + * @return + */ + public static Point getIntersectionOfTwoLines(double[] line1, double[] line2){ + // 两直线平行,没有交点 + if (Double.compare(line1[0],0.0) ==0 && Double.compare(line2[0],0.0) ==0){ + return null; + } + if (Double.compare(line1[0], 0.0) != 0 && Double.compare(line2[0], 0.0) != 0) { + if (Double.compare(ArithUtil.div(line1[1],line1[0]), ArithUtil.div(line2[1],line2[0]))==0){ + return null; + } + } + // 两直线不平行,有交点 + double x = (line1[1]*line2[2] - line2[1]*line1[2])/(line1[0]*line2[1] - line2[0]*line1[1]); + double y = (line2[0]*line1[2] - line1[0]*line2[2])/(line1[0]*line2[1] - line2[0]*line1[1]); + Point point = new Point(x,y); + return point; + } + + /** + * 获取两向量的点乘结果 + * @param vector1 + * @param vector2 + * @return + */ + public static double getMulOfVector(Vector vector1,Vector vector2) { + double mul1 = ArithUtil.mul(vector1.getX(), vector2.getX()); + double mul2 = ArithUtil.mul(vector1.getY(), vector2.getY()); + return ArithUtil.add(mul1,mul2); + } + + /** + * 判断点point是否在轮廓内,射线法 + * @param contour + * @param point + * @return true if point is in contour + */ + public static boolean isInPolygon(List contour, Point point){ + boolean flag = false; + // intersectionPoints为y=point.getY()直线与contour的交点 + Set intersectionPoints = new HashSet<>(); + for (LineSegment lineSegment:contour){ + Point point1 = getPoint(lineSegment, point.getY()); + if (point1!=null){ + intersectionPoints.add(point1); + } + } + int count = 0; + for (Point p:intersectionPoints){ + if (Double.compare(p.getX(),point.getX()) < 0){ + count++; + } + } + + // 是奇数,在轮廓里 + if (count%2!=0){ + flag = true; + } + return flag; + } + + /** + * 知道一个点的y坐标,获取该点坐标 + * @param lineSegment + * @param y + * @return + */ + public static Point getPoint(LineSegment lineSegment, double y){ + Point first = lineSegment.getStartPoint(); + Point second = lineSegment.getEndPoint(); + double x1 = first.getX(); + double y1 = first.getY(); + double x2 = second.getX(); + double y2 = second.getY(); + + if (Double.compare(y,y1) < 0 && Double.compare(y,y2) < 0){ + return null; + } + if (Double.compare(y,y1) > 0 && Double.compare(y,y2) > 0){ + return null; + } + if (Double.compare(y1,y2)==0 ){ + return null; + } + + double a = ArithUtil.div(ArithUtil.mul(ArithUtil.sub(y, y1), ArithUtil.sub(x2, x1)), ArithUtil.sub(y2, y1)); + double x = ArithUtil.add(a,x1); + + Point point = new Point(x,y); + return point; + } + + /** + * 返回A-->B的单位向量 + * @param A + * @param B + * @return + */ + public static Vector getNormalVectorFromTwoPoints(Point A, Point B){ +// System.out.println("A="+A+" B="+B); + Vector vector = new Vector(); + vector.setX(ArithUtil.sub(B.getX(),A.getX())); + vector.setY(ArithUtil.sub(B.getY(),A.getY())); + vector.normalize(); + return vector; + } + + /** + * 获取两个点之间的距离 + * @param point1 + * @param point2 + * @return + */ + public static double getDistanceBetweenTwoPoints(Point point1, Point point2){ + double x1 = point1.getX(); + double y1 = point1.getY(); + double x2 = point2.getX(); + double y2 = point2.getY(); + + return Math.sqrt(Math.pow(ArithUtil.sub(x1,x2),2)+ Math.pow(ArithUtil.sub(y1,y2),2)); + } + + /** + * 从两个点获取直线方程 + * + * @param one + * @param two + * @return + */ + public static double[] getLineFromTwoPoints(Point one, Point two) { + double x1 = one.getX(); + double y1 = one.getY(); + double x2 = two.getX(); + double y2 = two.getY(); + + double[] line = new double[3]; + line[0] = y2 - y1; + line[1] = x1 - x2; + line[2] = y1 * (x2 - x1) - x1 * (y2 - y1); + + return line; + } + +} diff --git a/src/com/lee/util/OffsetUtil.java b/src/com/lee/util/OffsetUtil.java new file mode 100644 index 0000000..98de766 --- /dev/null +++ b/src/com/lee/util/OffsetUtil.java @@ -0,0 +1,92 @@ +package com.lee.util; + +import com.lee.entity.LineSegment; +import com.lee.entity.Point; + +/** + * 获取两条线段的交点 + */ +public class OffsetUtil { + double[] ans = new double[0]; + + public Point intersection(LineSegment lineSegment1, LineSegment lineSegment2){ + Point first = lineSegment1.getStartPoint(); + Point second = lineSegment1.getEndPoint(); + Point first1 = lineSegment2.getStartPoint(); + Point second1 = lineSegment2.getEndPoint(); + + double[] start1 = new double[]{first.getX(),first.getY()}; + double[] end1 = new double[]{second.getX(),second.getY()}; + double[] start2 = new double[]{first1.getX(),first1.getY()}; + double[] end2 = new double[]{second1.getX(),second1.getY()}; + + double[] intersection = intersection(start1, end1, start2, end2); + + if (intersection.length == 0){ + return null; + } + return new Point(intersection[0],intersection[1]); + } + + public double[] intersection(double[] start1, double[] end1, double[] start2, double[] end2) { + double x1 = start1[0], y1 = start1[1]; + double x2 = end1[0], y2 = end1[1]; + double x3 = start2[0], y3 = start2[1]; + double x4 = end2[0], y4 = end2[1]; + + // 判断 (x1, y1)~(x2, y2) 和 (x3, y3)~(x4, y4) 是否平行 + if ((y4 - y3) * (x2 - x1) == (y2 - y1) * (x4 - x3)) { + // 若平行,则判断 (x3, y3) 是否在「直线」(x1, y1)~(x2, y2) 上 + if ((y2 - y1) * (x3 - x1) == (y3 - y1) * (x2 - x1)) { + // 判断 (x3, y3) 是否在「线段」(x1, y1)~(x2, y2) 上 + if (inside(x1, y1, x2, y2, x3, y3)) { + update(x3, y3); + } + // 判断 (x4, y4) 是否在「线段」(x1, y1)~(x2, y2) 上 + if (inside(x1, y1, x2, y2, x4, y4)) { + update(x4, y4); + } + // 判断 (x1, y1) 是否在「线段」(x3, y3)~(x4, y4) 上 + if (inside(x3, y3, x4, y4, x1, y1)) { + update(x1, y1); + } + // 判断 (x2, y2) 是否在「线段」(x3, y3)~(x4, y4) 上 + if (inside(x3, y3, x4, y4, x2, y2)) { + update(x2, y2); + } + } + // 在平行时,其余的所有情况都不会有交点 + } else { + // 联立方程得到 t1 和 t2 的值 + double t1 = + (double) (x3 * (y4 - y3) + y1 * (x4 - x3) - y3 * (x4 - x3) - x1 * (y4 - y3)) + / ((x2 - x1) * (y4 - y3) - (x4 - x3) * (y2 - y1)); + double t2 = + (double) (x1 * (y2 - y1) + y3 * (x2 - x1) - y1 * (x2 - x1) - x3 * (y2 - y1)) + / ((x4 - x3) * (y2 - y1) - (x2 - x1) * (y4 - y3)); + // 判断 t1 和 t2 是否均在 [0, 1] 之间 + if (t1 >= 0.0 && t1 <= 1.0 && t2 >= 0.0 && t2 <= 1.0) { + ans = new double[] {x1 + t1 * (x2 - x1), y1 + t1 * (y2 - y1)}; + } + } + return ans; + } + + // 判断 (xk, yk) 是否在「线段」(x1, y1)~(x2, y2) 上 + // 这里的前提是 (xk, yk) 一定在「直线」(x1, y1)~(x2, y2) 上 + public boolean inside(double x1, double y1, double x2, double y2, double xk, double yk) { + // 若与 x 轴平行,只需要判断 x 的部分 + // 若与 y 轴平行,只需要判断 y 的部分 + // 若为普通线段,则都要判断 + return (x1 == x2 || (Math.min(x1, x2) <= xk && xk <= Math.max(x1, x2))) + && (y1 == y2 || (Math.min(y1, y2) <= yk && yk <= Math.max(y1, y2))); + } + + public void update(double xk, double yk) { + // 将一个交点与当前 ans 中的结果进行比较 + // 若更优则替换 + if (ans.length == 0 || xk < ans[0] || (xk == ans[0] && yk < ans[1])) { + ans = new double[] {xk, yk}; + } + } +} diff --git a/test/com/lee/test/OffsetTest.java b/test/com/lee/test/OffsetTest.java new file mode 100644 index 0000000..8f6a4c2 --- /dev/null +++ b/test/com/lee/test/OffsetTest.java @@ -0,0 +1,62 @@ +package com.lee.test; + +import com.lee.algorithm.OffsetAlgorithm; +import com.lee.display.MainWindow; +import com.lee.entity.Point; +import com.lee.util.FileUtil; +import org.junit.Test; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class OffsetTest { + + @Test + public void testTriangle() throws IOException, InterruptedException { + offsetTest("file/triangle.txt",3.0,10); + TimeUnit.HOURS.sleep(1); + } + + @Test + public void testRectangle() throws IOException, InterruptedException { + offsetTest("file/rectangle.txt",3.0,20); + TimeUnit.HOURS.sleep(1); + } + + @Test + public void testStar() throws IOException, InterruptedException { + offsetTest("file/star.txt",3.0,10); + TimeUnit.HOURS.sleep(1); + } + + @Test + public void testTunnel() throws IOException, InterruptedException { + offsetTest("file/tunnel.txt",3.0,25); + TimeUnit.HOURS.sleep(1); + } + + @Test + public void test5Edge() throws IOException, InterruptedException { + offsetTest("file/5edge.txt",8.0,10); + TimeUnit.HOURS.sleep(1); + } + + public void offsetTest(String file, double distance,int layers) throws IOException { + // 读取文件,得到多边形轮廓点列表 + List points = FileUtil.readPoints(file); + List> contours = new ArrayList<>(); + contours.add(points); + // 逐层获取内缩轮廓 + for(int i = 1; i <= layers ; i++) { + List> lists = OffsetAlgorithm.offsetAlgorithm(points, i * distance); + if (lists!=null){ + contours.addAll(lists); + } + } + // 显示结果 + MainWindow mainWindow = new MainWindow(contours); + mainWindow.show(); + } +} diff --git a/test/com/lee/util/FileUtil.java b/test/com/lee/util/FileUtil.java new file mode 100644 index 0000000..5965c6c --- /dev/null +++ b/test/com/lee/util/FileUtil.java @@ -0,0 +1,27 @@ +package com.lee.util; + +import com.lee.entity.Point; + +import java.io.*; +import java.util.ArrayList; +import java.util.List; + +public class FileUtil { + /** + * 从路径file中读取多边形轮廓点 + * @param file + * @return + */ + public static List readPoints(String file) throws IOException { + List polygon = new ArrayList<>(); + + BufferedReader fileReader = new BufferedReader(new FileReader(file)); + String content; + while ((content = fileReader.readLine()) != null){ + String[] strings = content.split(","); + polygon.add(new Point(Double.valueOf(strings[0]),Double.valueOf(strings[1]))); + } + + return polygon; + } +}