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
+
+
+ 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;
+ }
+}