| | |
| | | boolean endInside = isPointInPolygon(s.end, polygon); |
| | | boolean intersects = segmentIntersectsBoundary(s.start, s.end, polygon); |
| | | if (!s.isMowing) { |
| | | // 非作业段统一替换为沿边界路径 |
| | | // 非作业段:若AB直线安全(不跨越边界且中点在内),保留直线;否则沿边界替换 |
| | | if (isSegmentSafe(s.start, s.end, polygon)) { |
| | | sanitized.add(s); |
| | | } else { |
| | | List<Point> path = getBoundaryPathWithSnap(s.start, s.end, polygon); |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | sanitized.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | } |
| | | } |
| | | } else { |
| | | if (!startInside || !endInside || intersects) { |
| | | SnapResult s1 = snapToBoundary(s.start, polygon); |
| | |
| | | } |
| | | |
| | | PathSegment s = lineSegmentsInRow.get(idxInRow); |
| | | // 首次连接或跨区连接均强制沿边界,避免穿越凹陷区 |
| | | // 首次或跨区连接:使用智能换行路径(优先直线最短,必要时沿边界) |
| | | if (Math.hypot(currentPos.x - s.start.x, currentPos.y - s.start.y) > 0.01) { |
| | | addBoundaryConnection(segments, currentPos, s.start, polygon); |
| | | addSafeConnection(segments, currentPos, s.start, polygon); |
| | | firstSegmentConnected = true; |
| | | } |
| | | segments.add(s); |
| | |
| | | } |
| | | |
| | | private static void addSafeConnection(List<PathSegment> segments, Point start, Point end, List<Point> polygon) { |
| | | if (!FORCE_BOUNDARY_TRAVEL && isSegmentSafe(start, end, polygon)) { |
| | | segments.add(new PathSegment(start, end, false)); |
| | | return; |
| | | // 使用新的智能换行路径算法 |
| | | List<PathSegment> connectionPath = generateSmartConnectionPath(start, end, polygon); |
| | | segments.addAll(connectionPath); |
| | | } |
| | | List<Point> path = getBoundaryPathWithSnap(start, end, polygon); |
| | | |
| | | /** |
| | | * 智能换行路径生成:根据AB线段与安全边界C的交点,生成混合路径 |
| | | * 逻辑: |
| | | * 1. 如果AB线段不穿越边界,直接返回AB直线 |
| | | * 2. 如果AB线段穿越边界,找到所有交点(如D, F, G, H),生成路径: |
| | | * A -> D -> (沿边界D到F) -> F -> G -> (沿边界G到H) -> H -> B |
| | | */ |
| | | private static List<PathSegment> generateSmartConnectionPath(Point A, Point B, List<Point> polygon) { |
| | | List<PathSegment> result = new ArrayList<>(); |
| | | |
| | | // 1. 检查AB直线是否安全(不穿越边界且中点在内部);与是否强制沿边界无关 |
| | | if (isSegmentSafe(A, B, polygon)) { |
| | | result.add(new PathSegment(A, B, false)); |
| | | return result; |
| | | } |
| | | |
| | | // 2. 获取AB与边界的所有交点 |
| | | List<IntersectionPoint> intersections = getSegmentBoundaryIntersections(A, B, polygon); |
| | | |
| | | // 3. 如果没有交点但不安全,说明整条线都在外部,强制沿边界 |
| | | if (intersections.isEmpty()) { |
| | | List<Point> path = getBoundaryPathWithSnap(A, B, polygon); |
| | | for (int i = 0; i < path.size() - 1; i++) { |
| | | segments.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | result.add(new PathSegment(path.get(i), path.get(i+1), false)); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | // 4. 根据交点成对处理,考虑A/B是否在内侧 |
| | | Point currentPos = A; |
| | | boolean startInside = isPointInPolygon(A, polygon); |
| | | boolean endInside = isPointInPolygon(B, polygon); |
| | | |
| | | for (int i = 0; i < intersections.size(); i += 2) { |
| | | IntersectionPoint entry = intersections.get(i); |
| | | |
| | | // 从当前位置到第一个交点:起点在内→直线;起点在外→沿边界到交点 |
| | | if (Math.hypot(currentPos.x - entry.point.x, currentPos.y - entry.point.y) > 1e-6) { |
| | | if (startInside) { |
| | | result.add(new PathSegment(currentPos, entry.point, false)); |
| | | } else { |
| | | List<Point> pathToEntry = getBoundaryPathWithSnap(currentPos, entry.point, polygon); |
| | | for (int j = 0; j < pathToEntry.size() - 1; j++) { |
| | | result.add(new PathSegment(pathToEntry.get(j), pathToEntry.get(j+1), false)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // 检查是否有配对的离开点 |
| | | if (i + 1 < intersections.size()) { |
| | | IntersectionPoint exit = intersections.get(i + 1); |
| | | |
| | | // 从进入点D到离开点F:沿边界行走 |
| | | List<Point> boundaryPath = getBoundaryPathBetweenPoints( |
| | | entry.point, entry.edgeIndex, |
| | | exit.point, exit.edgeIndex, |
| | | polygon |
| | | ); |
| | | |
| | | for (int j = 0; j < boundaryPath.size() - 1; j++) { |
| | | result.add(new PathSegment(boundaryPath.get(j), boundaryPath.get(j+1), false)); |
| | | } |
| | | |
| | | currentPos = exit.point; |
| | | // 之后的起点视为内侧 |
| | | startInside = true; |
| | | } else { |
| | | // 如果没有配对的离开点,说明终点在外部,沿边界到B |
| | | List<Point> path = getBoundaryPathWithSnap(entry.point, B, polygon); |
| | | for (int j = 0; j < path.size() - 1; j++) { |
| | | result.add(new PathSegment(path.get(j), path.get(j+1), false)); |
| | | } |
| | | return result; |
| | | } |
| | | } |
| | | |
| | | // 5. 从最后一个离开点到终点B:直线段(在内部) |
| | | if (Math.hypot(currentPos.x - B.x, currentPos.y - B.y) > 1e-6) { |
| | | if (endInside) { |
| | | result.add(new PathSegment(currentPos, B, false)); |
| | | } else { |
| | | List<Point> pathToB = getBoundaryPathWithSnap(currentPos, B, polygon); |
| | | for (int j = 0; j < pathToB.size() - 1; j++) { |
| | | result.add(new PathSegment(pathToB.get(j), pathToB.get(j+1), false)); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * 在已知边界边索引的情况下,沿边界获取两点之间的最短路径 |
| | | */ |
| | | private static List<Point> getBoundaryPathBetweenPoints( |
| | | Point start, int startEdgeIdx, |
| | | Point end, int endEdgeIdx, |
| | | List<Point> polygon) { |
| | | |
| | | int n = polygon.size(); |
| | | |
| | | // 如果在同一条边上,直接连接 |
| | | if (startEdgeIdx == endEdgeIdx) { |
| | | return Arrays.asList(start, end); |
| | | } |
| | | |
| | | // 正向路径(顺边) |
| | | List<Point> pathFwd = new ArrayList<>(); |
| | | pathFwd.add(start); |
| | | int curr = startEdgeIdx; |
| | | while (curr != endEdgeIdx) { |
| | | pathFwd.add(polygon.get((curr + 1) % n)); |
| | | curr = (curr + 1) % n; |
| | | } |
| | | pathFwd.add(end); |
| | | |
| | | // 反向路径(逆边) |
| | | List<Point> pathRev = new ArrayList<>(); |
| | | pathRev.add(start); |
| | | curr = startEdgeIdx; |
| | | while (curr != endEdgeIdx) { |
| | | pathRev.add(polygon.get(curr)); |
| | | curr = (curr - 1 + n) % n; |
| | | } |
| | | pathRev.add(end); |
| | | |
| | | // 返回几何长度更短的路径 |
| | | return getPathLength(pathFwd) <= getPathLength(pathRev) ? pathFwd : pathRev; |
| | | } |
| | | |
| | | // 强制沿边界绕行的连接(不做直线安全判断),用来在同一扫描行的多个作业段之间跳转 |
| | |
| | | return ccw(a, c, d) != ccw(b, c, d) && ccw(a, b, c) != ccw(a, b, d); |
| | | } |
| | | |
| | | // 获取线段AB与边界多边形的所有交点,按照距离A点的顺序排序 |
| | | private static class IntersectionPoint { |
| | | Point point; // 交点坐标 |
| | | int edgeIndex; // 交点所在的边界边索引 |
| | | double distFromStart; // 距离起点A的距离 |
| | | boolean isEntry; // true表示进入边界,false表示离开边界 |
| | | |
| | | IntersectionPoint(Point p, int idx, double dist, boolean entry) { |
| | | this.point = p; |
| | | this.edgeIndex = idx; |
| | | this.distFromStart = dist; |
| | | this.isEntry = entry; |
| | | } |
| | | } |
| | | |
| | | // 计算线段AB与多边形边界的所有交点 |
| | | private static List<IntersectionPoint> getSegmentBoundaryIntersections(Point A, Point B, List<Point> polygon) { |
| | | List<IntersectionPoint> intersections = new ArrayList<>(); |
| | | for (int i = 0; i < polygon.size(); i++) { |
| | | Point C = polygon.get(i); |
| | | Point D = polygon.get((i + 1) % polygon.size()); |
| | | Point intersection = getLineSegmentIntersection(A, B, C, D); |
| | | if (intersection != null) { |
| | | if (isSamePoint(intersection, A) || isSamePoint(intersection, B)) continue; |
| | | double dist = Math.hypot(intersection.x - A.x, intersection.y - A.y); |
| | | intersections.add(new IntersectionPoint(intersection, i, dist, false)); |
| | | } |
| | | } |
| | | // 距离排序 |
| | | Collections.sort(intersections, (i1, i2) -> Double.compare(i1.distFromStart, i2.distFromStart)); |
| | | // 去重近似相同交点(顶点双交) |
| | | List<IntersectionPoint> deduped = new ArrayList<>(); |
| | | for (IntersectionPoint ip : intersections) { |
| | | boolean dup = false; |
| | | for (IntersectionPoint kept : deduped) { |
| | | if (Math.abs(ip.point.x - kept.point.x) < 1e-6 && Math.abs(ip.point.y - kept.point.y) < 1e-6) { dup = true; break; } |
| | | } |
| | | if (!dup) deduped.add(ip); |
| | | } |
| | | return deduped; |
| | | } |
| | | |
| | | // 计算两条线段的交点(如果存在) |
| | | private static Point getLineSegmentIntersection(Point p1, Point p2, Point p3, Point p4) { |
| | | double x1 = p1.x, y1 = p1.y; |
| | | double x2 = p2.x, y2 = p2.y; |
| | | double x3 = p3.x, y3 = p3.y; |
| | | double x4 = p4.x, y4 = p4.y; |
| | | |
| | | double denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4); |
| | | if (Math.abs(denom) < 1e-10) return null; // 平行或重合 |
| | | |
| | | double t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom; |
| | | double u = -((x1 - x2) * (y1 - y3) - (y1 - y2) * (x1 - x3)) / denom; |
| | | |
| | | // 检查交点是否在两条线段上(使用略微宽松的范围以处理浮点误差) |
| | | double epsilon = 1e-6; |
| | | if (t >= -epsilon && t <= 1 + epsilon && u >= -epsilon && u <= 1 + epsilon) { |
| | | return new Point(x1 + t * (x2 - x1), y1 + t * (y2 - y1)); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | private static boolean ccw(Point a, Point b, Point c) { |
| | | return (c.y - a.y) * (b.x - a.x) > (b.y - a.y) * (c.x - a.x); |
| | | } |