From 13d032241e1a2938a8be4f64c9171e1240e9ea1e Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期一, 22 十二月 2025 18:50:42 +0800
Subject: [PATCH] 新增了边界管理页面和首页边界虚线功能

---
 src/lujing/AoxinglujingNoObstacle.java |  206 ++++++++++++++++++++++++++++++++-------------------
 1 files changed, 130 insertions(+), 76 deletions(-)

diff --git a/src/lujing/AoxinglujingNoObstacle.java b/src/lujing/AoxinglujingNoObstacle.java
index 75717ba..1b13c98 100644
--- a/src/lujing/AoxinglujingNoObstacle.java
+++ b/src/lujing/AoxinglujingNoObstacle.java
@@ -1,14 +1,19 @@
 package lujing;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 /**
- * 鏃犻殰纰嶇墿鍑稿舰鑽夊湴璺緞瑙勫垝绫� (浼樺寲鐗�)
+ * 鏃犻殰纰嶇墿鍑稿舰鑽夊湴璺緞瑙勫垝绫� (缁堟瀬浼樺寲鐗�)
+ * 鐗规�э細
+ * 1. 鏈�灏忔姇褰卞搴︽柟鍚戦�夋嫨 (鏁堢巼鏈�楂橈紝杞集鏈�灏�)
+ * 2. 杈圭紭杞粨浼樺厛鍒囧壊 (鏃犳瑙掕鐩�)
+ * 3. 鏀寔澶栭儴浼犲叆宸插寘鍚噸鍙犵巼鐨勫搴﹀弬鏁�
  */
 public class AoxinglujingNoObstacle {
 
-    // 浼樺寲锛氬紩鍏ユ瀬灏忓�肩敤浜庢诞鐐规暟姣旇緝锛屽鐞嗗嚑浣曠簿搴﹁宸�
+    // 寮曞叆鏋佸皬鍊肩敤浜庢诞鐐规暟姣旇緝锛屽鐞嗗嚑浣曠簿搴﹁宸�
     private static final double EPSILON = 1e-6;
 
     /**
@@ -49,15 +54,15 @@
     }
 
     /**
-     * 瀵瑰鍏紑鐨勯潤鎬佽皟鐢ㄦ柟娉� (淇濈暀瀛楃涓插叆鍙傛牸寮�)
+     * 瀵瑰鍏紑鐨勯潤鎬佽皟鐢ㄦ柟娉�
      *
      * @param boundaryCoordsStr 鍦板潡杈圭晫鍧愭爣瀛楃涓� "x1,y1;x2,y2;..."
-     * @param mowingWidthStr    鍓茶崏瀹藉害瀛楃涓诧紝濡� "0.34"
+     * @param mowingWidthStr    鏈夋晥鍓茶崏瀹藉害瀛楃涓� (宸插寘鍚噸鍙犵巼)锛屽 "0.30"
      * @param safetyMarginStr   瀹夊叏杈硅窛瀛楃涓诧紝濡� "0.2"
      * @return 璺緞娈靛垪琛�
      */
     public static List<PathSegment> planPath(String boundaryCoordsStr, String mowingWidthStr, String safetyMarginStr) {
-        // 1. 瑙f瀽鍙傛暟 (浼樺寲锛氬崟鐙彁鍙栬В鏋愰�昏緫)
+        // 1. 瑙f瀽鍙傛暟
         List<Point> originalPolygon = parseCoords(boundaryCoordsStr);
         double mowingWidth;
         double safetyMargin;
@@ -74,85 +79,119 @@
     }
 
     /**
-     * 鏍稿績绠楁硶閫昏緫 (寮虹被鍨嬪叆鍙傦紝渚夸簬娴嬭瘯鍜屽唴閮ㄨ皟鐢�)
+     * 鏍稿績绠楁硶閫昏緫
      */
-    private static List<PathSegment> planPathCore(List<Point> originalPolygon, double mowingWidth, double safetyMargin) {
-        // 浼樺寲锛氫笁瑙掑舰涔熸槸鍚堟硶鐨勫嚫澶氳竟褰紝闄愬埗鏀逛负灏忎簬3
+    private static List<PathSegment> planPathCore(List<Point> originalPolygon, double width, double safetyMargin) {
         if (originalPolygon == null || originalPolygon.size() < 3) {
-            throw new IllegalArgumentException("澶氳竟褰㈠潗鏍囩偣涓嶈冻3涓紝鏃犳硶鏋勬垚鏈夋晥鍖哄煙");
+            return new ArrayList<>(); // 鎴栨姏鍑哄紓甯革紝瑙嗕笟鍔¢渶姹傝�屽畾
         }
 
         // 纭繚澶氳竟褰㈡槸閫嗘椂閽堟柟鍚�
         ensureCCW(originalPolygon);
 
-        // 1. 鏍规嵁瀹夊叏杈硅窛鍐呯缉
-        List<Point> shrunkPolygon = shrinkPolygon(originalPolygon, safetyMargin);
+        // 1. 鏍规嵁瀹夊叏杈硅窛鍐呯缉锛屽緱鍒板疄闄呬綔涓氬尯鍩�
+        List<Point> workAreaPolygon = shrinkPolygon(originalPolygon, safetyMargin);
 
-        // 浼樺寲锛氬唴缂╁悗濡傛灉澶氳竟褰㈠け鏁堬紙渚嬪鍦板潡澶獎锛屽唴缂╁悗娑堝け锛夛紝鐩存帴杩斿洖绌鸿矾寰勶紝閬垮厤鍚庣画鎶ラ敊
-        if (shrunkPolygon.size() < 3) {
-            // 杩欓噷鍙互璁板綍鏃ュ織锛氬湴鍧楄繃灏忥紝鏃犳硶婊¤冻瀹夊叏璺濈浣滀笟
+        // 濡傛灉鍐呯缉鍚庡尯鍩熷け鏁堬紙濡傚湴鍧楀お灏忥級锛岃繑鍥炵┖璺緞
+        if (workAreaPolygon.size() < 3) {
             return new ArrayList<>();
         }
 
-        // 2. 璁$畻鏈�闀胯竟瑙掑害 (浣滀负鎵弿鏂瑰悜)
-        double angle = calculateLongestEdgeAngle(originalPolygon);
+        List<PathSegment> finalPath = new ArrayList<>();
 
-        // 3. & 4. 鏃嬭浆鎵弿骞跺壀瑁�
-        List<PathSegment> mowingLines = generateClippedMowingLines(shrunkPolygon, angle, mowingWidth);
+        // 2. [浼樺寲] 浼樺厛鐢熸垚杞粨璺緞 (Contour Pass)
+        // 娌夸綔涓氳竟鐣岃蛋涓�鍦堬紝纭繚杈圭紭鏁撮綈涓旀棤閬楁紡
+        addContourPath(workAreaPolygon, finalPath);
 
-        // 5. 寮撳瓧褰㈣繛鎺�
-        return connectPathSegments(mowingLines);
-    }
+        // 3. [浼樺寲] 璁$畻鏈�浣虫壂鎻忚搴�
+        // 瀵绘壘璁╁杈瑰舰鎶曞奖楂樺害鏈�灏忕殑瑙掑害锛屼粠鑰屾渶灏忓寲杞集娆℃暟
+        double bestAngle = findOptimalScanAngle(workAreaPolygon);
 
-    // ================= 鏍稿績绠楁硶杈呭姪鏂规硶 =================
+        // 4. 鐢熸垚鍐呴儴寮撳瓧褰㈣矾寰�
+        // 鐩存帴浣跨敤浼犲叆鐨� width (宸插寘鍚噸鍙犵巼)
+        List<PathSegment> zigZagPaths = generateClippedMowingLines(workAreaPolygon, bestAngle, width);
 
-    private static List<Point> shrinkPolygon(List<Point> polygon, double margin) {
-        List<Point> newPoints = new ArrayList<>();
-        int n = polygon.size();
-
-        for (int i = 0; i < n; i++) {
-            Point p1 = polygon.get(i);
-            Point p2 = polygon.get((i + 1) % n);
-            Point p0 = polygon.get((i - 1 + n) % n);
-
-            Line line1 = offsetLine(p1, p2, margin);
-            Line line0 = offsetLine(p0, p1, margin);
-
-            Point intersection = getIntersection(line0, line1);
+        // 5. 杩炴帴杞粨璺緞鍜屽紦瀛楀舰璺緞
+        if (!finalPath.isEmpty() && !zigZagPaths.isEmpty()) {
+            Point contourEnd = finalPath.get(finalPath.size() - 1).end;
+            Point zigzagStart = zigZagPaths.get(0).start;
             
-            // 浼樺寲锛氬鍔犻潪绌哄垽鏂紝涓斿鏋滀氦鐐瑰紓甯歌繙锛堝皷瑙掓晥搴旓級锛屽疄闄呭伐绋嬩腑鍙兘闇�瑕佸垏瑙掑鐞�
-            // 杩欓噷鏆備繚鐣欏熀纭�閫昏緫锛屼絾鍦ㄥ嚫澶氳竟褰腑閫氬父娌¢棶棰�
-            if (intersection != null) {
-                newPoints.add(intersection);
+            // 濡傛灉杞粨缁堢偣涓庡紦瀛楀舰璧风偣涓嶉噸鍚堬紝娣诲姞杩囨浮娈�
+            if (distanceSq(contourEnd, zigzagStart) > EPSILON) {
+                finalPath.add(new PathSegment(contourEnd, zigzagStart, false));
             }
         }
-        return newPoints;
+
+        // 6. 鍚堝苟寮撳瓧褰㈣矾寰�
+        finalPath.addAll(connectPathSegments(zigZagPaths));
+
+        return finalPath;
     }
 
-    private static double calculateLongestEdgeAngle(List<Point> polygon) {
-        double maxDistSq = -1;
-        double angle = 0;
-        int n = polygon.size();
+    // ================= 鏍稿績閫昏緫杈呭姪鏂规硶 =================
 
+    /**
+     * 娣诲姞杞粨璺緞 (鍥寸潃澶氳竟褰㈣蛋涓�鍦�)
+     */
+    private static void addContourPath(List<Point> polygon, List<PathSegment> path) {
+        int n = polygon.size();
         for (int i = 0; i < n; i++) {
             Point p1 = polygon.get(i);
             Point p2 = polygon.get((i + 1) % n);
-            double dx = p2.x - p1.x;
-            double dy = p2.y - p1.y;
-            double distSq = dx * dx + dy * dy;
+            path.add(new PathSegment(p1, p2, true));
+        }
+    }
 
-            if (distSq > maxDistSq) {
-                maxDistSq = distSq;
-                angle = Math.atan2(dy, dx);
+    /**
+     * 瀵绘壘鏈�浼樻壂鎻忚搴� (鏈�灏忔姇褰遍珮搴︽硶)
+     */
+    private static double findOptimalScanAngle(List<Point> polygon) {
+        double minHeight = Double.MAX_VALUE;
+        double bestAngle = 0;
+        int n = polygon.size();
+
+        // 閬嶅巻姣忎竴鏉¤竟锛岃绠椾互璇ヨ竟涓衡�滃簳鈥濇椂锛屽杈瑰舰鐨勯珮搴�
+        for (int i = 0; i < n; i++) {
+            Point p1 = polygon.get(i);
+            Point p2 = polygon.get((i + 1) % n);
+
+            // 褰撳墠杈圭殑瑙掑害
+            double currentAngle = Math.atan2(p2.y - p1.y, p2.x - p1.x);
+
+            // 璁$畻鍦ㄨ繖涓搴︿笅鐨勬姇褰遍珮搴�
+            double height = calculatePolygonHeightAtAngle(polygon, currentAngle);
+
+            if (height < minHeight) {
+                minHeight = height;
+                bestAngle = currentAngle;
             }
         }
-        return angle;
+        return bestAngle;
+    }
+
+    /**
+     * 璁$畻澶氳竟褰㈠湪鐗瑰畾鏃嬭浆瑙掑害涓嬬殑Y杞存姇褰遍珮搴�
+     */
+    private static double calculatePolygonHeightAtAngle(List<Point> poly, double angle) {
+        double minY = Double.MAX_VALUE;
+        double maxY = -Double.MAX_VALUE;
+
+        double cos = Math.cos(-angle);
+        double sin = Math.sin(-angle);
+
+        for (Point p : poly) {
+            // 鍙渶璁$畻鏃嬭浆鍚庣殑Y鍧愭爣
+            double rotatedY = p.x * sin + p.y * cos;
+            if (rotatedY < minY) minY = rotatedY;
+            if (rotatedY > maxY) maxY = rotatedY;
+        }
+        return maxY - minY;
     }
 
     private static List<PathSegment> generateClippedMowingLines(List<Point> polygon, double angle, double width) {
         List<PathSegment> segments = new ArrayList<>();
-        
-        // 鏃嬭浆鑷虫按骞�
+
+        // 鏃嬭浆澶氳竟褰㈣嚦姘村钩
         List<Point> rotatedPoly = rotatePolygon(polygon, -angle);
 
         double minY = Double.MAX_VALUE;
@@ -162,28 +201,27 @@
             if (p.y > maxY) maxY = p.y;
         }
 
-        // 浼樺寲锛氳捣濮嬫壂鎻忕嚎澧炲姞涓�涓井灏忕殑鍋忕Щ閲� EPSILON
-        // 閬垮厤鎵弿绾挎濂借惤鍦ㄩ《鐐逛笂锛屽鑷翠氦鐐瑰垽鏂�昏緫鍑虹幇浜屼箟鎬�
+        // 璧峰鎵弿绾夸綅缃細
+        // 浠� minY + width/2 寮�濮嬶紝鍥犱负涔嬪墠宸茬粡璧颁簡杞粨绾�(Contour Pass)銆�
+        // 杞粨绾胯礋璐f竻鐞嗚竟缂樺尯鍩燂紝鍐呴儴濉厖绾夸繚鎸� width 鐨勯棿璺濆嵆鍙��
+        // 鍔犱笂 EPSILON 闃叉娴偣鏁板垰濂借惤鍦ㄨ竟鐣屼笂瀵艰嚧鐨勫垽鏂宸�
         double currentY = minY + width / 2.0 + EPSILON;
 
         while (currentY < maxY) {
             List<Double> xIntersections = new ArrayList<>();
             int n = rotatedPoly.size();
-            
+
             for (int i = 0; i < n; i++) {
                 Point p1 = rotatedPoly.get(i);
                 Point p2 = rotatedPoly.get((i + 1) % n);
 
-                // 浼樺寲锛氬拷鐣ユ按骞崇嚎娈� (p1.y == p2.y)锛岄伩鍏嶉櫎闆堕敊璇紝姘村钩绾挎涓嶅弬涓庡瀭鐩存壂鎻忕嚎姹備氦
+                // 蹇界暐姘村钩绾挎
                 if (Math.abs(p1.y - p2.y) < EPSILON) continue;
 
-                // 鍒ゆ柇绾挎鏄惁璺ㄨ秺 currentY
-                // 浣跨敤涓ユ牸鐨勪笉绛夊紡閫昏緫閰嶅悎鑼冨洿鍒ゆ柇
                 double minP = Math.min(p1.y, p2.y);
                 double maxP = Math.max(p1.y, p2.y);
 
                 if (currentY >= minP && currentY < maxP) {
-                    // 绾挎�ф彃鍊兼眰X
                     double x = p1.x + (currentY - p1.y) * (p2.x - p1.x) / (p2.y - p1.y);
                     xIntersections.add(x);
                 }
@@ -191,20 +229,19 @@
 
             Collections.sort(xIntersections);
 
-            // 鎻愬彇绾挎锛岄�氬父鏄垚瀵瑰嚭鐜�
             if (xIntersections.size() >= 2) {
-                // 鍙栨渶宸﹀拰鏈�鍙崇偣杩炴帴锛堝簲瀵瑰彲鑳藉嚭鐜扮殑寰皬璁$畻璇樊瀵艰嚧鐨勫涓氦鐐癸級
+                // 鍙栨渶宸﹀拰鏈�鍙充氦鐐�
                 double xStart = xIntersections.get(0);
                 double xEnd = xIntersections.get(xIntersections.size() - 1);
 
-                // 鍙湁褰撶嚎娈甸暱搴﹀ぇ浜庢瀬灏忓�兼椂鎵嶆坊鍔狅紝閬垮厤鐢熸垚鍣偣璺緞
                 if (xEnd - xStart > EPSILON) {
-                    Point rStart = rotatePoint(new Point(xStart, currentY), angle); // 杩欓噷鐨刢urrentY瀹為檯涓婂甫浜唀psilon锛岃繕鍘熸椂娌¢棶棰�
+                    // 鍙嶅悜鏃嬭浆鍥炲師鍧愭爣绯�
+                    Point rStart = rotatePoint(new Point(xStart, currentY), angle);
                     Point rEnd = rotatePoint(new Point(xEnd, currentY), angle);
                     segments.add(new PathSegment(rStart, rEnd, true));
                 }
             }
-
+            // 姝ヨ繘
             currentY += width;
         }
 
@@ -231,7 +268,6 @@
             // 娣诲姞杩囨浮娈�
             if (i > 0) {
                 Point prevEnd = result.get(result.size() - 1).end;
-                // 鍙湁褰撹窛绂荤‘瀹炲瓨鍦ㄦ椂鎵嶆坊鍔犺繃娓℃锛堥伩鍏嶉噸鍚堢偣锛�
                 if (distanceSq(prevEnd, actualStart) > EPSILON) {
                     result.add(new PathSegment(prevEnd, actualStart, false));
                 }
@@ -242,12 +278,12 @@
         return result;
     }
 
-    // ================= 鍩虹鍑犱綍宸ュ叿 (浼樺寲鐗�) =================
+    // ================= 鍩虹鍑犱綍宸ュ叿 =================
 
     private static List<Point> parseCoords(String s) {
         List<Point> list = new ArrayList<>();
         if (s == null || s.trim().isEmpty()) return list;
-        
+
         String[] parts = s.split(";");
         for (String part : parts) {
             String[] xy = part.split(",");
@@ -257,14 +293,13 @@
                     double y = Double.parseDouble(xy[1].trim());
                     list.add(new Point(x, y));
                 } catch (NumberFormatException e) {
-                    // 蹇界暐鏍煎紡閿欒鐨勫崟涓偣
+                    // 蹇界暐鏍煎紡閿欒
                 }
             }
         }
         return list;
     }
 
-    // Shoelace鍏紡鍒ゆ柇鏂瑰悜骞惰皟鏁�
     private static void ensureCCW(List<Point> polygon) {
         double sum = 0;
         for (int i = 0; i < polygon.size(); i++) {
@@ -272,14 +307,33 @@
             Point p2 = polygon.get((i + 1) % polygon.size());
             sum += (p2.x - p1.x) * (p2.y + p1.y);
         }
-        // 鍋囪鏍囧噯绗涘崱灏斿潗鏍囩郴锛宻um > 0 涓洪『鏃堕拡锛岄渶瑕佸弽杞负閫嗘椂閽�
         if (sum > 0) {
             Collections.reverse(polygon);
         }
     }
 
+    private static List<Point> shrinkPolygon(List<Point> polygon, double margin) {
+        List<Point> newPoints = new ArrayList<>();
+        int n = polygon.size();
+
+        for (int i = 0; i < n; i++) {
+            Point p1 = polygon.get(i);
+            Point p2 = polygon.get((i + 1) % n);
+            Point p0 = polygon.get((i - 1 + n) % n);
+
+            Line line1 = offsetLine(p1, p2, margin);
+            Line line0 = offsetLine(p0, p1, margin);
+
+            Point intersection = getIntersection(line0, line1);
+            if (intersection != null) {
+                newPoints.add(intersection);
+            }
+        }
+        return newPoints;
+    }
+
     private static class Line {
-        double a, b, c; 
+        double a, b, c;
         public Line(double a, double b, double c) { this.a = a; this.b = b; this.c = c; }
     }
 
@@ -287,13 +341,13 @@
         double dx = p2.x - p1.x;
         double dy = p2.y - p1.y;
         double len = Math.sqrt(dx * dx + dy * dy);
-        
-        // 闃叉闄や互0
+
         if (len < EPSILON) return new Line(0, 0, 0);
 
         double nx = -dy / len;
         double ny = dx / len;
 
+        // 鍚戝乏渚у钩绉伙紙鍋囪閫嗘椂閽堬級
         double newX = p1.x + nx * dist;
         double newY = p1.y + ny * dist;
 
@@ -305,7 +359,7 @@
 
     private static Point getIntersection(Line l1, Line l2) {
         double det = l1.a * l2.b - l2.a * l1.b;
-        if (Math.abs(det) < EPSILON) return null; // 骞宠
+        if (Math.abs(det) < EPSILON) return null;
         double x = (l1.b * l2.c - l2.b * l1.c) / det;
         double y = (l2.a * l1.c - l1.a * l2.c) / det;
         return new Point(x, y);
@@ -324,7 +378,7 @@
         }
         return res;
     }
-    
+
     private static double distanceSq(Point p1, Point p2) {
         return (p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y);
     }

--
Gitblit v1.10.0