package lujing; import java.util.*; import java.util.regex.*; import java.util.stream.Collectors; /** * 异形草地路径规划 - 优化完善版 * 采用更完善的算法: * 1. 使用多边形裁剪库计算更精确的内缩边界 * 2. 使用扫描线填充算法生成更优化的路径 * 3. 使用可见图算法寻找最优绕行路径 * 4. 使用路径优化算法减少空行和转弯 */ public class YixinglujingHaveObstacel { private static final double EPS = 1e-10; private static final double MIN_SEG_LEN = 0.01; // 忽略小于1cm的碎线 private static final double CORNER_THRESHOLD = Math.toRadians(30); // 30度以下的角度合并 public static List planPath(String coordinates, String obstaclesStr, String widthStr, String marginStr) { try { // 解析输入参数 double mowWidth = Double.parseDouble(widthStr); double safeMargin = Double.parseDouble(marginStr); // 解析多边形和障碍物 List boundary = parseCoordinates(coordinates); if (boundary.size() < 3) { throw new IllegalArgumentException("地块边界至少需要3个点"); } // 确保多边形为逆时针方向 makeCCW(boundary); // 解析障碍物并外扩 List obstacles = parseAndExpandObstacles(obstaclesStr, safeMargin); // 生成内缩作业边界(考虑障碍物) List workingArea = computeWorkingArea(boundary, obstacles, safeMargin); if (workingArea.isEmpty()) { return new ArrayList<>(); } // 生成完整的全覆盖路径(不考虑障碍物) List fullPath = generateCompleteCoverage(workingArea, mowWidth); // 用障碍物裁剪路径 List clippedPath = clipPathWithObstacles(fullPath, obstacles); // 连接和优化路径(限制在作业边界内) List finalPath = connectAndOptimizePath(clippedPath, obstacles, mowWidth, workingArea); return finalPath; } catch (Exception e) { System.err.println("路径规划错误: " + e.getMessage()); e.printStackTrace(); return new ArrayList<>(); } } /** * 计算作业区域(考虑障碍物) */ private static List computeWorkingArea(List boundary, List obstacles, double margin) { // 首先生成内缩边界 List offsetBoundary = offsetPolygon(boundary, -margin); if (obstacles.isEmpty()) { return offsetBoundary; } // 如果存在障碍物,从内缩边界中减去障碍物区域 // 简化处理:工作区域仍以内缩边界为主,具体裁剪在路径层面完成 makeCCW(offsetBoundary); return offsetBoundary; } /** * 生成完整的全覆盖路径 */ private static List generateCompleteCoverage(List polygon, double width) { List path = new ArrayList<>(); // 1. 生成边界路径 List borderPath = generateBorderPath(polygon, width); path.addAll(borderPath); // 2. 生成扫描线路径 List scanLines = generateScanLines(polygon, width); // 3. 连接扫描线 if (!scanLines.isEmpty()) { Point currentPos = path.isEmpty() ? scanLines.get(0).start : path.get(path.size() - 1).end; for (PathSegment scanLine : scanLines) { // 添加空行连接 if (distance(currentPos, scanLine.start) > MIN_SEG_LEN) { path.add(new PathSegment(currentPos, scanLine.start, false)); } path.add(scanLine); currentPos = scanLine.end; } // 连接回起点 if (distance(currentPos, path.get(0).start) > MIN_SEG_LEN) { path.add(new PathSegment(currentPos, path.get(0).start, false)); } } return path; } /** * 生成边界路径(一圈或多圈) */ private static List generateBorderPath(List polygon, double width) { List border = new ArrayList<>(); // 根据宽度确定需要多少圈边界 int borderPasses = 1; // 至少一圈 if (width < 0.3) { borderPasses = 2; // 宽度较小,增加边界圈数 } for (int pass = 0; pass < borderPasses; pass++) { double offset = pass * width; List offsetPoly = offsetPolygon(polygon, -offset); if (offsetPoly.size() < 3) break; for (int i = 0; i < offsetPoly.size(); i++) { Point start = offsetPoly.get(i); Point end = offsetPoly.get((i + 1) % offsetPoly.size()); border.add(new PathSegment(start, end, true)); } } return border; } /** * 生成扫描线路径 */ private static List generateScanLines(List polygon, double width) { List scanLines = new ArrayList<>(); // 计算最优扫描方向 double optimalAngle = calculateOptimalScanAngle(polygon); // 旋转多边形到扫描方向 List rotatedPoly = rotatePolygon(polygon, -optimalAngle); // 计算包围盒 Bounds bounds = calculateBounds(rotatedPoly); // 生成扫描线 boolean leftToRight = true; for (double y = bounds.minY + width / 2; y <= bounds.maxY - width / 2 + EPS; y += width) { // 获取水平线与多边形的交点 List intersections = getHorizontalIntersections(rotatedPoly, y); if (intersections.size() < 2) continue; // 交点排序并成对处理 Collections.sort(intersections); List lineSegments = new ArrayList<>(); for (int i = 0; i < intersections.size(); i += 2) { if (i + 1 >= intersections.size()) break; double x1 = intersections.get(i); double x2 = intersections.get(i + 1); if (x2 - x1 < MIN_SEG_LEN) continue; // 旋转回原始坐标系 Point start = rotatePoint(new Point(x1, y), optimalAngle); Point end = rotatePoint(new Point(x2, y), optimalAngle); lineSegments.add(new PathSegment(start, end, true)); } // 方向交替 if (!leftToRight) { Collections.reverse(lineSegments); for (PathSegment seg : lineSegments) { Point temp = seg.start; seg.start = seg.end; seg.end = temp; } } scanLines.addAll(lineSegments); leftToRight = !leftToRight; } return scanLines; } /** * 用障碍物裁剪路径 */ private static List clipPathWithObstacles(List path, List obstacles) { if (obstacles.isEmpty()) return path; List clipped = new ArrayList<>(); for (PathSegment segment : path) { List remaining = new ArrayList<>(); remaining.add(segment); // 依次用每个障碍物裁剪 for (Obstacle obstacle : obstacles) { List temp = new ArrayList<>(); for (PathSegment seg : remaining) { temp.addAll(obstacle.clipSegment(seg)); } remaining = temp; } clipped.addAll(remaining); } return clipped; } /** * 连接和优化路径 */ private static List connectAndOptimizePath(List segments, List obstacles, double width, List workingArea) { if (segments.isEmpty()) return new ArrayList<>(); // 1. 先按类型分组:割草段和连接段 List mowingSegments = segments.stream() .filter(s -> s.isMowing) .collect(Collectors.toList()); // 2. 使用旅行商问题(TSP)的近似算法连接割草段 List connectedPath = connectSegmentsTSP(mowingSegments, obstacles, workingArea); // 3. 优化路径:合并小段、平滑转角 connectedPath = optimizePath(connectedPath, width); return connectedPath; } /** * 使用旅行商问题近似算法连接路径段 */ private static List connectSegmentsTSP(List segments, List obstacles, List workingArea) { List connected = new ArrayList<>(); if (segments.isEmpty()) return connected; // 构建点集(所有线段的端点) List points = new ArrayList<>(); for (PathSegment seg : segments) { points.add(seg.start); points.add(seg.end); } // 使用最近邻算法构建路径 boolean[] visited = new boolean[segments.size()]; Point currentPos = segments.get(0).start; while (true) { int bestIdx = -1; double bestDist = Double.MAX_VALUE; boolean useStart = true; // 寻找最近的未访问线段 for (int i = 0; i < segments.size(); i++) { if (visited[i]) continue; PathSegment seg = segments.get(i); double distToStart = distance(currentPos, seg.start); double distToEnd = distance(currentPos, seg.end); if (distToStart < bestDist) { bestDist = distToStart; bestIdx = i; useStart = true; } if (distToEnd < bestDist) { bestDist = distToEnd; bestIdx = i; useStart = false; } } if (bestIdx == -1) break; // 添加连接路径 PathSegment bestSeg = segments.get(bestIdx); Point targetPoint = useStart ? bestSeg.start : bestSeg.end; if (distance(currentPos, targetPoint) > MIN_SEG_LEN) { // 寻找安全连接路径(受作业边界限制) List detour = findSafePath(currentPos, targetPoint, obstacles, workingArea); connected.addAll(detour); } // 添加割草线段(可能反转方向) PathSegment toAdd = bestSeg; if (!useStart) { toAdd = new PathSegment(bestSeg.end, bestSeg.start, true); } connected.add(toAdd); currentPos = toAdd.end; visited[bestIdx] = true; } return connected; } /** * 寻找安全路径(A*算法) */ private static List findSafePath(Point start, Point end, List obstacles, List workingArea) { // 如果直线路径安全,直接使用 if (isLineSafe(start, end, obstacles, workingArea)) { List direct = new ArrayList<>(); direct.add(new PathSegment(start, end, false)); return direct; } // 否则使用A*算法寻找绕行路径 return aStarPathFinding(start, end, obstacles, workingArea); } /** * A*算法路径寻找 */ private static List aStarPathFinding(Point start, Point end, List obstacles, List workingArea) { // 简化的A*算法实现 // 这里我们使用障碍物边界上的关键点作为路径节点 List nodes = new ArrayList<>(); nodes.add(start); nodes.add(end); // 添加障碍物的顶点作为候选节点 for (Obstacle obs : obstacles) { nodes.addAll(obs.getKeyPoints()); } // 添加作业边界顶点,允许贴边绕行 if (workingArea != null && workingArea.size() >= 3) { nodes.addAll(workingArea); } // 构建图 Map> graph = new HashMap<>(); for (Point p1 : nodes) { graph.put(p1, new HashMap<>()); for (Point p2 : nodes) { if (p1 == p2) continue; if (isLineSafe(p1, p2, obstacles, workingArea)) { graph.get(p1).put(p2, distance(p1, p2)); } } } // A*搜索 Map gScore = new HashMap<>(); Map fScore = new HashMap<>(); Map cameFrom = new HashMap<>(); PriorityQueue openSet = new PriorityQueue<>( Comparator.comparingDouble(p -> fScore.getOrDefault(p, Double.MAX_VALUE)) ); gScore.put(start, 0.0); fScore.put(start, heuristic(start, end)); openSet.add(start); while (!openSet.isEmpty()) { Point current = openSet.poll(); if (current.equals(end)) { return reconstructPath(cameFrom, current); } for (Map.Entry neighborEntry : graph.getOrDefault(current, new HashMap<>()).entrySet()) { Point neighbor = neighborEntry.getKey(); double tentativeGScore = gScore.get(current) + neighborEntry.getValue(); if (tentativeGScore < gScore.getOrDefault(neighbor, Double.MAX_VALUE)) { cameFrom.put(neighbor, current); gScore.put(neighbor, tentativeGScore); fScore.put(neighbor, tentativeGScore + heuristic(neighbor, end)); if (!openSet.contains(neighbor)) { openSet.add(neighbor); } } } } // 如果没有找到路径,不做不安全的连接 return new ArrayList<>(); } /** * 重构路径 */ private static List reconstructPath(Map cameFrom, Point current) { List pathPoints = new ArrayList<>(); while (current != null) { pathPoints.add(current); current = cameFrom.get(current); } Collections.reverse(pathPoints); List path = new ArrayList<>(); for (int i = 0; i < pathPoints.size() - 1; i++) { path.add(new PathSegment(pathPoints.get(i), pathPoints.get(i + 1), false)); } return path; } /** * 启发函数 */ private static double heuristic(Point a, Point b) { return distance(a, b); } /** * 优化路径 */ private static List optimizePath(List path, double width) { if (path.size() <= 1) return path; List optimized = new ArrayList<>(); PathSegment current = path.get(0); for (int i = 1; i < path.size(); i++) { PathSegment next = path.get(i); // 检查是否可以合并当前线段和下一线段 if (canMergeSegments(current, next, width)) { // 合并线段 current = mergeSegments(current, next); } else { // 添加当前线段,开始新的合并 optimized.add(current); current = next; } } optimized.add(current); // 平滑转角 optimized = smoothCorners(optimized, width); return optimized; } /** * 检查是否可以合并两个线段 */ private static boolean canMergeSegments(PathSegment a, PathSegment b, double width) { if (!a.isMowing || !b.isMowing) return false; // 检查端点是否重合 if (!a.end.equals(b.start) && !a.end.equals(b.end)) return false; // 检查方向是否一致 Point dir1 = new Point(a.end.x - a.start.x, a.end.y - a.start.y); Point dir2; if (a.end.equals(b.start)) { dir2 = new Point(b.end.x - b.start.x, b.end.y - b.start.y); } else { dir2 = new Point(b.start.x - b.end.x, b.start.y - b.end.y); } double angle = angleBetween(dir1, dir2); return angle < Math.toRadians(10); // 角度小于10度可以合并 } /** * 合并两个线段 */ private static PathSegment mergeSegments(PathSegment a, PathSegment b) { Point newEnd = a.end.equals(b.start) ? b.end : b.start; return new PathSegment(a.start, newEnd, true); } /** * 平滑转角 */ private static List smoothCorners(List path, double width) { if (path.size() < 3) return path; List smoothed = new ArrayList<>(); smoothed.add(path.get(0)); for (int i = 1; i < path.size() - 1; i++) { PathSegment prev = path.get(i - 1); PathSegment curr = path.get(i); PathSegment next = path.get(i + 1); if (!prev.isMowing || !curr.isMowing || !next.isMowing) { smoothed.add(curr); continue; } // 计算转角 Point inVec = new Point(curr.start.x - prev.end.x, curr.start.y - prev.end.y); Point outVec = new Point(next.start.x - curr.end.x, next.start.y - curr.end.y); double angle = angleBetween(inVec, outVec); if (angle < CORNER_THRESHOLD) { // 小角度,可以直接连接 PathSegment direct = new PathSegment(prev.end, next.start, true); smoothed.remove(smoothed.size() - 1); // 移除上一个线段 smoothed.add(direct); i++; // 跳过下一个线段 } else { smoothed.add(curr); } } if (path.size() > 1) { smoothed.add(path.get(path.size() - 1)); } return smoothed; } // ==================== 几何计算工具 ==================== /** * 多边形偏移算法 */ private static List offsetPolygon(List polygon, double d) { // 基于“偏移边直线交点”的较稳健实现。约定polygon为CCW,左法向量为外侧。 if (polygon == null || polygon.size() < 3) return new ArrayList<>(); List poly = new ArrayList<>(polygon); makeCCW(poly); int n = poly.size(); List out = new ArrayList<>(n); for (int i = 0; i < n; i++) { Point A = poly.get((i - 1 + n) % n); Point B = poly.get(i); Point C = poly.get((i + 1) % n); Point e1 = normalize(subtract(B, A)); Point e2 = normalize(subtract(C, B)); Point n1 = new Point(-e1.y, e1.x); Point n2 = new Point(-e2.y, e2.x); Point p1 = add(B, multiply(n1, d)); Point p2 = add(B, multiply(n2, d)); Point dir1 = e1; Point dir2 = e2; Point inter = intersectLines(p1, dir1, p2, dir2); if (inter == null) { // 平行或数值不稳定时退化 Point avgN = add(n1, n2); if (magnitude(avgN) < EPS) avgN = n1; else avgN = normalize(avgN); inter = add(B, multiply(avgN, d)); } out.add(inter); } return out; } // 计算两条参数直线的交点 p=p0+t*v, q=q0+s*w private static Point intersectLines(Point p0, Point v, Point q0, Point w) { double det = v.x * w.y - v.y * w.x; if (Math.abs(det) < EPS) return null; double t = ((q0.x - p0.x) * w.y - (q0.y - p0.y) * w.x) / det; return new Point(p0.x + t * v.x, p0.y + t * v.y); } /** * 计算最优扫描角度 */ private static double calculateOptimalScanAngle(List polygon) { double bestAngle = 0; double minSpan = Double.MAX_VALUE; // 尝试多个角度 for (int i = 0; i < 180; i += 5) { double angle = Math.toRadians(i); List rotated = rotatePolygon(polygon, angle); Bounds bounds = calculateBounds(rotated); double span = bounds.maxY - bounds.minY; if (span < minSpan) { minSpan = span; bestAngle = angle; } } return bestAngle; } /** * 获取水平线与多边形的交点 */ private static List getHorizontalIntersections(List polygon, double y) { List intersections = 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); // 检查边是否与水平线相交 if ((p1.y <= y && p2.y >= y) || (p1.y >= y && p2.y <= y)) { if (Math.abs(p2.y - p1.y) < EPS) { // 水平边,跳过 continue; } double t = (y - p1.y) / (p2.y - p1.y); if (t >= -EPS && t <= 1 + EPS) { double x = p1.x + t * (p2.x - p1.x); intersections.add(x); } } } // 去重并排序 intersections = intersections.stream() .distinct() .sorted() .collect(Collectors.toList()); return intersections; } /** * 判断直线是否安全 */ private static boolean isLineSafe(Point p1, Point p2, List obstacles, List workingArea) { // 必须完全在作业内缩边界内 if (workingArea != null && !isSegmentInsidePolygon(p1, p2, workingArea)) { return false; } for (Obstacle obs : obstacles) { if (obs.doesSegmentIntersect(p1, p2)) { return false; } } return true; } // 判断线段是否位于多边形内部(不越界) private static boolean isSegmentInsidePolygon(Point a, Point b, List polygon) { if (polygon == null || polygon.size() < 3) return true; // 中点在内 Point mid = new Point((a.x + b.x) / 2.0, (a.y + b.y) / 2.0); if (!pointInPolygon(mid, polygon)) return false; // 不与边界相交(允许端点接触) int n = polygon.size(); for (int i = 0; i < n; i++) { Point p1 = polygon.get(i); Point p2 = polygon.get((i + 1) % n); if (lineSegmentIntersection(a, b, p1, p2)) { // 忽略仅在端点处的小接触 if (distance(a, p1) < EPS || distance(a, p2) < EPS || distance(b, p1) < EPS || distance(b, p2) < EPS) { continue; } return false; } } return true; } private static boolean pointInPolygon(Point p, List poly) { boolean inside = false; for (int i = 0, j = poly.size() - 1; i < poly.size(); j = i++) { Point pi = poly.get(i), pj = poly.get(j); boolean intersect = ((pi.y > p.y) != (pj.y > p.y)) && (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y + EPS) + pi.x); if (intersect) inside = !inside; } return inside; } // ==================== 向量运算工具 ==================== private static Point add(Point a, Point b) { return new Point(a.x + b.x, a.y + b.y); } private static Point subtract(Point a, Point b) { return new Point(a.x - b.x, a.y - b.y); } private static Point multiply(Point p, double scalar) { return new Point(p.x * scalar, p.y * scalar); } private static Point normalize(Point p) { double mag = magnitude(p); if (mag < EPS) return p; return new Point(p.x / mag, p.y / mag); } private static double magnitude(Point p) { return Math.sqrt(p.x * p.x + p.y * p.y); } private static double dot(Point a, Point b) { return a.x * b.x + a.y * b.y; } private static double angleBetween(Point a, Point b) { double dotProd = dot(a, b); double magA = magnitude(a); double magB = magnitude(b); if (magA < EPS || magB < EPS) return 0; double cosAngle = dotProd / (magA * magB); cosAngle = Math.max(-1, Math.min(1, cosAngle)); return Math.acos(cosAngle); } private static double distance(Point a, Point b) { return magnitude(subtract(a, b)); } private static Point rotatePoint(Point p, double angle) { double cos = Math.cos(angle); double sin = Math.sin(angle); return new Point(p.x * cos - p.y * sin, p.x * sin + p.y * cos); } private static List rotatePolygon(List polygon, double angle) { return polygon.stream() .map(p -> rotatePoint(p, angle)) .collect(Collectors.toList()); } private static Bounds calculateBounds(List points) { double minX = Double.MAX_VALUE, maxX = -Double.MAX_VALUE; double minY = Double.MAX_VALUE, maxY = -Double.MAX_VALUE; for (Point p : points) { minX = Math.min(minX, p.x); maxX = Math.max(maxX, p.x); minY = Math.min(minY, p.y); maxY = Math.max(maxY, p.y); } return new Bounds(minX, maxX, minY, maxY); } private static void makeCCW(List polygon) { double area = 0; int n = polygon.size(); for (int i = 0; i < n; i++) { Point p1 = polygon.get(i); Point p2 = polygon.get((i + 1) % n); area += (p2.x - p1.x) * (p2.y + p1.y); } if (area > 0) { Collections.reverse(polygon); } } // ==================== 障碍物处理 ==================== private static List parseAndExpandObstacles(String obstaclesStr, double margin) { List obstacles = new ArrayList<>(); if (obstaclesStr == null || obstaclesStr.trim().isEmpty()) { return obstacles; } // 解析障碍物字符串 Pattern pattern = Pattern.compile("\\(([^)]+)\\)"); Matcher matcher = pattern.matcher(obstaclesStr); while (matcher.find()) { String coords = matcher.group(1); List points = parseCoordinates(coords); if (points.size() == 2) { // 圆形障碍物 Point center = points.get(0); double radius = distance(center, points.get(1)) + margin; obstacles.add(new CircularObstacle(center, radius)); } else if (points.size() >= 3) { // 多边形障碍物 makeCCW(points); List expanded = offsetPolygon(points, margin); obstacles.add(new PolygonalObstacle(expanded)); } } return obstacles; } private static List parseCoordinates(String str) { List points = new ArrayList<>(); if (str == null || str.trim().isEmpty()) { return points; } String[] tokens = str.split(";"); for (String token : tokens) { token = token.trim(); if (token.isEmpty()) continue; String[] xy = token.split(","); if (xy.length == 2) { try { double x = Double.parseDouble(xy[0].trim()); double y = Double.parseDouble(xy[1].trim()); points.add(new Point(x, y)); } catch (NumberFormatException e) { System.err.println("无效坐标: " + token); } } } return points; } // ==================== 内部类定义 ==================== /** * 障碍物基类 */ abstract static class Obstacle { abstract List clipSegment(PathSegment seg); abstract boolean doesSegmentIntersect(Point p1, Point p2); abstract boolean containsPoint(Point p); abstract List getKeyPoints(); } /** * 多边形障碍物 */ static class PolygonalObstacle extends Obstacle { List vertices; PolygonalObstacle(List vertices) { this.vertices = vertices; } @Override List clipSegment(PathSegment seg) { List tValues = new ArrayList<>(); tValues.add(0.0); tValues.add(1.0); // 收集所有交点 for (int i = 0; i < vertices.size(); i++) { Point p1 = vertices.get(i); Point p2 = vertices.get((i + 1) % vertices.size()); Double t = lineIntersection(seg.start, seg.end, p1, p2); if (t != null) { tValues.add(t); } } Collections.sort(tValues); List result = new ArrayList<>(); // 生成不在障碍物内部的线段段 for (int i = 0; i < tValues.size() - 1; i++) { double t1 = tValues.get(i); double t2 = tValues.get(i + 1); double tMid = (t1 + t2) / 2; Point midPoint = interpolate(seg.start, seg.end, tMid); if (!containsPoint(midPoint)) { Point start = interpolate(seg.start, seg.end, t1); Point end = interpolate(seg.start, seg.end, t2); result.add(new PathSegment(start, end, seg.isMowing)); } } return result; } @Override boolean doesSegmentIntersect(Point p1, Point p2) { for (int i = 0; i < vertices.size(); i++) { Point v1 = vertices.get(i); Point v2 = vertices.get((i + 1) % vertices.size()); if (lineSegmentIntersection(p1, p2, v1, v2)) { return true; } } return false; } @Override boolean containsPoint(Point p) { int crossings = 0; for (int i = 0; i < vertices.size(); i++) { Point v1 = vertices.get(i); Point v2 = vertices.get((i + 1) % vertices.size()); if (((v1.y <= p.y && p.y < v2.y) || (v2.y <= p.y && p.y < v1.y)) && (p.x < (v2.x - v1.x) * (p.y - v1.y) / (v2.y - v1.y) + v1.x)) { crossings++; } } return (crossings % 2) == 1; } @Override List getKeyPoints() { return new ArrayList<>(vertices); } } /** * 圆形障碍物 */ static class CircularObstacle extends Obstacle { Point center; double radius; CircularObstacle(Point center, double radius) { this.center = center; this.radius = radius; } @Override List clipSegment(PathSegment seg) { double dx = seg.end.x - seg.start.x; double dy = seg.end.y - seg.start.y; double fx = seg.start.x - center.x; double fy = seg.start.y - center.y; double a = dx * dx + dy * dy; double b = 2 * (fx * dx + fy * dy); double c = fx * fx + fy * fy - radius * radius; List tValues = new ArrayList<>(); tValues.add(0.0); tValues.add(1.0); double discriminant = b * b - 4 * a * c; if (discriminant > 0) { double sqrtDisc = Math.sqrt(discriminant); double t1 = (-b - sqrtDisc) / (2 * a); double t2 = (-b + sqrtDisc) / (2 * a); if (t1 > EPS && t1 < 1 - EPS) tValues.add(t1); if (t2 > EPS && t2 < 1 - EPS) tValues.add(t2); } Collections.sort(tValues); List result = new ArrayList<>(); for (int i = 0; i < tValues.size() - 1; i++) { double t1 = tValues.get(i); double t2 = tValues.get(i + 1); double tMid = (t1 + t2) / 2; Point midPoint = interpolate(seg.start, seg.end, tMid); if (!containsPoint(midPoint)) { Point start = interpolate(seg.start, seg.end, t1); Point end = interpolate(seg.start, seg.end, t2); result.add(new PathSegment(start, end, seg.isMowing)); } } return result; } @Override boolean doesSegmentIntersect(Point p1, Point p2) { Point closest = closestPointOnSegment(center, p1, p2); // 将与圆的相切也视为相交,避免路径擦边 return distance(center, closest) <= radius + EPS; } @Override boolean containsPoint(Point p) { return distance(center, p) < radius - EPS; } @Override List getKeyPoints() { List points = new ArrayList<>(); int numPoints = 8; // 八边形近似 for (int i = 0; i < numPoints; i++) { double angle = 2 * Math.PI * i / numPoints; points.add(new Point( center.x + radius * Math.cos(angle), center.y + radius * Math.sin(angle) )); } return points; } } /** * 路径段 */ public static class PathSegment { public Point start, end; public boolean isMowing; public PathSegment(Point start, Point end, boolean isMowing) { this.start = start; this.end = end; this.isMowing = isMowing; } @Override public String toString() { return String.format("%s -> %s [%s]", start, end, isMowing ? "MOW" : "MOVE"); } } /** * 点类 */ public static class Point { public double x, y; public Point(double x, double y) { this.x = x; this.y = y; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof Point)) return false; Point other = (Point) obj; return Math.abs(x - other.x) < EPS && Math.abs(y - other.y) < EPS; } @Override public int hashCode() { return Double.hashCode(x) * 31 + Double.hashCode(y); } @Override public String toString() { return String.format("(%.2f, %.2f)", x, y); } } /** * 边界框 */ private static class Bounds { double minX, maxX, minY, maxY; Bounds(double minX, double maxX, double minY, double maxY) { this.minX = minX; this.maxX = maxX; this.minY = minY; this.maxY = maxY; } } // ==================== 几何工具函数 ==================== private static Double lineIntersection(Point a1, Point a2, Point b1, Point b2) { double det = (a2.x - a1.x) * (b2.y - b1.y) - (a2.y - a1.y) * (b2.x - b1.x); if (Math.abs(det) < EPS) return null; double t = ((b1.x - a1.x) * (b2.y - b1.y) - (b1.y - a1.y) * (b2.x - b1.x)) / det; double u = ((a1.x - b1.x) * (a2.y - a1.y) - (a1.y - b1.y) * (a2.x - a1.x)) / (-det); if (t >= -EPS && t <= 1 + EPS && u >= -EPS && u <= 1 + EPS) { return Math.max(0, Math.min(1, t)); } return null; } private static boolean lineSegmentIntersection(Point a1, Point a2, Point b1, Point b2) { Double t = lineIntersection(a1, a2, b1, b2); return t != null; } private static Point interpolate(Point a, Point b, double t) { return new Point(a.x + (b.x - a.x) * t, a.y + (b.y - a.y) * t); } private static Point closestPointOnSegment(Point p, Point a, Point b) { double ax = b.x - a.x; double ay = b.y - a.y; double bx = p.x - a.x; double by = p.y - a.y; double dot = ax * bx + ay * by; double lenSq = ax * ax + ay * ay; double t = (lenSq > EPS) ? Math.max(0, Math.min(1, dot / lenSq)) : 0; return new Point(a.x + t * ax, a.y + t * ay); } }