package zhangaiwu; import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.List; import java.util.ArrayList; /** * 障碍物绘制类 - 负责绘制地块中的障碍物 * * 注意:障碍物图层需要处于地块和导航路径上方。 * 在 MapRenderer.renderMap() 中,绘制顺序应为: * 1. 地块边界(底层) * 2. 导航路径(中层) * 3. 障碍物(顶层,显示在地块和导航路径上方) */ public class Obstacledraw { // 颜色定义 private static final Color CIRCLE_FILL_COLOR = new Color(128, 128, 128, 128); // 圆形填充色 - 灰色,透明度50% private static final Color CIRCLE_BORDER_COLOR = new Color(199, 21, 133); // 圆形边框色 - 深粉红 private static final Color POLYGON_FILL_COLOR = new Color(128, 128, 128, 128); // 多边形填充色 - 灰色,透明度50% private static final Color POLYGON_BORDER_COLOR = new Color(25, 25, 112); // 多边形边框色 - 深蓝 private static final Color OBSTACLE_LABEL_COLOR = Color.BLACK; private static final Color OBSTACLE_POINT_COLOR = Color.RED; // 尺寸定义 private static final double OBSTACLE_POINT_SIZE = 0.1; // 障碍物控制点大小(米) // 边界线宽度与地块边界线宽度一致:3 / Math.max(0.5, scale) private static final float BOUNDARY_STROKE_BASE = 3.0f; // 与地块边界线宽度一致 private static final float SELECTED_WIDTH_MULTIPLIER = 1.5f; // 选中时的宽度倍数 /** * 绘制地块的所有障碍物 * * @param g2d 图形上下文 * @param obstacles 障碍物列表 * @param scale 缩放比例 * @param selectedObstacleName 选中的障碍物名称(可为null) */ public static void drawObstacles(Graphics2D g2d, List obstacles, double scale, String selectedObstacleName) { if (obstacles == null || obstacles.isEmpty()) { return; } // 保存原始变换 AffineTransform originalTransform = g2d.getTransform(); // 应用反锯齿 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 绘制每个障碍物 for (Obstacledge.Obstacle obstacle : obstacles) { if (obstacle == null || !obstacle.isValid()) { continue; } // 检查是否被选中 boolean isSelected = selectedObstacleName != null && selectedObstacleName.equals(obstacle.getObstacleName()); // 根据形状绘制 switch (obstacle.getShape()) { case CIRCLE: drawCircleObstacle(g2d, obstacle, scale, isSelected); break; case POLYGON: drawPolygonObstacle(g2d, obstacle, scale, isSelected); break; } // 绘制障碍物标签 drawObstacleLabel(g2d, obstacle, scale); } // 恢复原始变换 g2d.setTransform(originalTransform); } /** * 绘制圆形障碍物 */ private static void drawCircleObstacle(Graphics2D g2d, Obstacledge.Obstacle obstacle, double scale, boolean isSelected) { List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.size() < 2) { return; } // 获取圆心和圆上一点 Obstacledge.XYCoordinate center = xyCoords.get(0); Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1); // 计算半径 double dx = pointOnCircle.getX() - center.getX(); double dy = pointOnCircle.getY() - center.getY(); double radius = Math.sqrt(dx * dx + dy * dy); if (radius <= 0) { return; } // 计算绘制位置 double x = center.getX() - radius; double y = center.getY() - radius; double diameter = radius * 2; // 绘制圆形 Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter); // 设置填充颜色 g2d.setColor(CIRCLE_FILL_COLOR); g2d.fill(circle); // 设置边框颜色和宽度(与地块边界线宽度一致) float strokeWidth = (float)(BOUNDARY_STROKE_BASE / Math.max(0.5, scale)); if (isSelected) { g2d.setColor(CIRCLE_BORDER_COLOR.darker()); // 选中时稍微加粗 g2d.setStroke(new BasicStroke(strokeWidth * SELECTED_WIDTH_MULTIPLIER)); } else { g2d.setColor(CIRCLE_BORDER_COLOR); g2d.setStroke(new BasicStroke(strokeWidth)); } g2d.draw(circle); // 绘制圆心和控制点 drawControlPoints(g2d, obstacle, scale, isSelected); } /** * 绘制多边形障碍物 */ private static void drawPolygonObstacle(Graphics2D g2d, Obstacledge.Obstacle obstacle, double scale, boolean isSelected) { List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.size() < 3) { return; } // 创建多边形路径 Path2D.Double polygon = new Path2D.Double(); // 移动到第一个点 Obstacledge.XYCoordinate firstCoord = xyCoords.get(0); polygon.moveTo(firstCoord.getX(), firstCoord.getY()); // 添加其他点 for (int i = 1; i < xyCoords.size(); i++) { Obstacledge.XYCoordinate coord = xyCoords.get(i); polygon.lineTo(coord.getX(), coord.getY()); } // 闭合多边形 polygon.closePath(); // 设置填充颜色 g2d.setColor(POLYGON_FILL_COLOR); g2d.fill(polygon); // 设置边框颜色和宽度(与地块边界线宽度一致) float strokeWidth = (float)(BOUNDARY_STROKE_BASE / Math.max(0.5, scale)); if (isSelected) { g2d.setColor(POLYGON_BORDER_COLOR.darker()); // 选中时稍微加粗 g2d.setStroke(new BasicStroke(strokeWidth * SELECTED_WIDTH_MULTIPLIER)); } else { g2d.setColor(POLYGON_BORDER_COLOR); g2d.setStroke(new BasicStroke(strokeWidth)); } g2d.draw(polygon); // 绘制控制点 drawControlPoints(g2d, obstacle, scale, isSelected); } /** * 绘制障碍物控制点 */ private static void drawControlPoints(Graphics2D g2d, Obstacledge.Obstacle obstacle, double scale, boolean isSelected) { List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return; } // 设置控制点颜色和大小 g2d.setColor(OBSTACLE_POINT_COLOR); double pointSize = OBSTACLE_POINT_SIZE; // 如果是选中的障碍物,绘制所有控制点 if (isSelected) { for (Obstacledge.XYCoordinate coord : xyCoords) { double x = coord.getX() - pointSize / 2; double y = coord.getY() - pointSize / 2; Ellipse2D.Double point = new Ellipse2D.Double(x, y, pointSize, pointSize); g2d.fill(point); } } // 否则,只绘制第一个控制点(圆形障碍物为圆心,多边形障碍物为第一个顶点) else { Obstacledge.XYCoordinate firstCoord = xyCoords.get(0); double x = firstCoord.getX() - pointSize / 2; double y = firstCoord.getY() - pointSize / 2; Ellipse2D.Double point = new Ellipse2D.Double(x, y, pointSize, pointSize); g2d.fill(point); } } /** * 绘制障碍物标签 * 文字大小与"缩放"文字一致(11号字体),且不随地图缩放变化 */ private static void drawObstacleLabel(Graphics2D g2d, Obstacledge.Obstacle obstacle, double scale) { List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return; } // 保存当前变换 AffineTransform originalTransform = g2d.getTransform(); double centerX; double centerY; Obstacledge.ObstacleShape shape = obstacle.getShape(); if (shape == Obstacledge.ObstacleShape.CIRCLE) { Obstacledge.XYCoordinate centerCoord = xyCoords.get(0); centerX = centerCoord.getX(); centerY = centerCoord.getY(); } else { Point2D.Double centroid = computePolygonCentroid(xyCoords); centerX = centroid.x; centerY = centroid.y; } // 将世界坐标转换为屏幕坐标 Point2D.Double worldPoint = new Point2D.Double(centerX, centerY); Point2D.Double screenPoint = new Point2D.Double(); originalTransform.transform(worldPoint, screenPoint); // 恢复原始变换以使用屏幕坐标绘制 g2d.setTransform(new AffineTransform()); // 获取障碍物名称 String obstacleName = obstacle.getObstacleName(); if (obstacleName == null || obstacleName.trim().isEmpty()) { obstacleName = "障碍物"; } else { obstacleName = obstacleName.trim(); } // 设置字体和颜色(与"缩放"文字一致) g2d.setColor(OBSTACLE_LABEL_COLOR); g2d.setFont(new Font("微软雅黑", Font.PLAIN, 11)); // 与"缩放"文字大小一致 // 绘制标签 String label = obstacleName; FontMetrics metrics = g2d.getFontMetrics(); int textWidth = metrics.stringWidth(label); int textHeight = metrics.getHeight(); // 在屏幕坐标中心点绘制标签 int textX = (int)(screenPoint.x - textWidth / 2.0); int textY = (int)(screenPoint.y + textHeight / 4.0); // 稍微向下偏移 g2d.drawString(label, textX, textY); // 恢复原始变换 g2d.setTransform(originalTransform); } private static Point2D.Double computePolygonCentroid(List xyCoords) { double area = 0.0; double cx = 0.0; double cy = 0.0; int n = xyCoords.size(); for (int i = 0; i < n; i++) { Obstacledge.XYCoordinate current = xyCoords.get(i); Obstacledge.XYCoordinate next = xyCoords.get((i + 1) % n); double x0 = current.getX(); double y0 = current.getY(); double x1 = next.getX(); double y1 = next.getY(); double cross = x0 * y1 - x1 * y0; area += cross; cx += (x0 + x1) * cross; cy += (y0 + y1) * cross; } double areaFactor = area * 0.5; if (Math.abs(areaFactor) < 1e-9) { double avgX = 0.0; double avgY = 0.0; for (Obstacledge.XYCoordinate coord : xyCoords) { avgX += coord.getX(); avgY += coord.getY(); } int size = Math.max(1, xyCoords.size()); return new Point2D.Double(avgX / size, avgY / size); } double centroidX = cx / (6.0 * areaFactor); double centroidY = cy / (6.0 * areaFactor); return new Point2D.Double(centroidX, centroidY); } /** * 检查点是否在障碍物内 * * @param worldPoint 世界坐标点 * @param obstacle 障碍物 * @return 如果点在障碍物内返回true,否则返回false */ public static boolean isPointInObstacle(Point2D.Double worldPoint, Obstacledge.Obstacle obstacle) { if (worldPoint == null || obstacle == null || !obstacle.isValid()) { return false; } List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return false; } // 根据障碍物形状进行判断 switch (obstacle.getShape()) { case CIRCLE: return isPointInCircleObstacle(worldPoint, xyCoords); case POLYGON: return isPointInPolygonObstacle(worldPoint, xyCoords); default: return false; } } /** * 检查点是否在圆形障碍物内 */ private static boolean isPointInCircleObstacle(Point2D.Double worldPoint, List xyCoords) { if (xyCoords.size() < 2) { return false; } // 圆心 Obstacledge.XYCoordinate center = xyCoords.get(0); // 圆上一点 Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1); // 计算半径 double dx = pointOnCircle.getX() - center.getX(); double dy = pointOnCircle.getY() - center.getY(); double radius = Math.sqrt(dx * dx + dy * dy); // 计算点到圆心的距离 double pointDx = worldPoint.x - center.getX(); double pointDy = worldPoint.y - center.getY(); double distance = Math.sqrt(pointDx * pointDx + pointDy * pointDy); return distance <= radius; } /** * 检查点是否在多边形障碍物内 */ private static boolean isPointInPolygonObstacle(Point2D.Double worldPoint, List xyCoords) { if (xyCoords.size() < 3) { return false; } // 使用射线法判断点是否在多边形内 boolean inside = false; int n = xyCoords.size(); for (int i = 0, j = n - 1; i < n; j = i++) { double xi = xyCoords.get(i).getX(); double yi = xyCoords.get(i).getY(); double xj = xyCoords.get(j).getX(); double yj = xyCoords.get(j).getY(); boolean intersect = ((yi > worldPoint.y) != (yj > worldPoint.y)) && (worldPoint.x < (xj - xi) * (worldPoint.y - yi) / (yj - yi) + xi); if (intersect) { inside = !inside; } } return inside; } /** * 检查点是否在障碍物的控制点上 * * @param worldPoint 世界坐标点 * @param obstacle 障碍物 * @param scale 缩放比例 * @return 控制点索引(从0开始),如果不在控制点上返回-1 */ public static int getControlPointIndex(Point2D.Double worldPoint, Obstacledge.Obstacle obstacle, double scale) { if (worldPoint == null || obstacle == null || !obstacle.isValid()) { return -1; } List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return -1; } // 根据障碍物形状计算选择阈值 double selectionThreshold = OBSTACLE_POINT_SIZE * 2 / scale; // 检查每个控制点 for (int i = 0; i < xyCoords.size(); i++) { Obstacledge.XYCoordinate coord = xyCoords.get(i); double dx = worldPoint.x - coord.getX(); double dy = worldPoint.y - coord.getY(); double distance = Math.sqrt(dx * dx + dy * dy); if (distance <= selectionThreshold) { return i; } } return -1; } /** * 获取障碍物的边界框 * * @param obstacle 障碍物 * @return 边界框(minX, minY, maxX, maxY),如果无效返回null */ public static double[] getObstacleBounds(Obstacledge.Obstacle obstacle) { if (obstacle == null || !obstacle.isValid()) { return null; } List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return null; } double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double maxX = -Double.MAX_VALUE; double maxY = -Double.MAX_VALUE; // 根据障碍物形状计算边界 switch (obstacle.getShape()) { case CIRCLE: return getCircleObstacleBounds(xyCoords); case POLYGON: return getPolygonObstacleBounds(xyCoords); default: return null; } } /** * 获取圆形障碍物的边界框 */ private static double[] getCircleObstacleBounds(List xyCoords) { if (xyCoords.size() < 2) { return null; } // 圆心 Obstacledge.XYCoordinate center = xyCoords.get(0); // 圆上一点 Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1); // 计算半径 double dx = pointOnCircle.getX() - center.getX(); double dy = pointOnCircle.getY() - center.getY(); double radius = Math.sqrt(dx * dx + dy * dy); // 计算边界 double minX = center.getX() - radius; double minY = center.getY() - radius; double maxX = center.getX() + radius; double maxY = center.getY() + radius; return new double[]{minX, minY, maxX, maxY}; } /** * 获取多边形障碍物的边界框 */ private static double[] getPolygonObstacleBounds(List xyCoords) { if (xyCoords.size() < 3) { return null; } double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double maxX = -Double.MAX_VALUE; double maxY = -Double.MAX_VALUE; for (Obstacledge.XYCoordinate coord : xyCoords) { minX = Math.min(minX, coord.getX()); minY = Math.min(minY, coord.getY()); maxX = Math.max(maxX, coord.getX()); maxY = Math.max(maxY, coord.getY()); } return new double[]{minX, minY, maxX, maxY}; } /** * 将障碍物列表转换为绘图用的点列表 * * @param obstacles 障碍物列表 * @return 所有障碍物控制点的列表 */ public static List getObstaclePoints(List obstacles) { List points = new ArrayList<>(); if (obstacles == null || obstacles.isEmpty()) { return points; } for (Obstacledge.Obstacle obstacle : obstacles) { if (obstacle == null || !obstacle.isValid()) { continue; } List xyCoords = obstacle.getXyCoordinates(); for (Obstacledge.XYCoordinate coord : xyCoords) { points.add(new Point2D.Double(coord.getX(), coord.getY())); } } return points; } /** * 计算所有障碍物的总边界框 * * @param obstacles 障碍物列表 * @return 边界框(minX, minY, maxX, maxY),如果没有障碍物返回null */ public static double[] getAllObstaclesBounds(List obstacles) { if (obstacles == null || obstacles.isEmpty()) { return null; } double minX = Double.MAX_VALUE; double minY = Double.MAX_VALUE; double maxX = -Double.MAX_VALUE; double maxY = -Double.MAX_VALUE; boolean hasBounds = false; for (Obstacledge.Obstacle obstacle : obstacles) { double[] bounds = getObstacleBounds(obstacle); if (bounds != null) { minX = Math.min(minX, bounds[0]); minY = Math.min(minY, bounds[1]); maxX = Math.max(maxX, bounds[2]); maxY = Math.max(maxY, bounds[3]); hasBounds = true; } } if (!hasBounds) { return null; } return new double[]{minX, minY, maxX, maxY}; } /** * 绘制障碍物编辑模式下的辅助线 * * @param g2d 图形上下文 * @param obstacle 障碍物 * @param scale 缩放比例 * @param isDragging 是否正在拖拽 * @param dragPointIndex 拖拽的控制点索引 */ public static void drawEditingGuide(Graphics2D g2d, Obstacledge.Obstacle obstacle, double scale, boolean isDragging, int dragPointIndex) { if (obstacle == null || !obstacle.isValid()) { return; } List xyCoords = obstacle.getXyCoordinates(); if (xyCoords.isEmpty()) { return; } // 设置辅助线样式 g2d.setColor(new Color(0, 100, 0, 150)); // 半透明的深绿色 g2d.setStroke(new BasicStroke(1.0f / (float)scale, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 1.0f, new float[]{5.0f / (float)scale, 5.0f / (float)scale}, 0.0f)); // 根据障碍物形状绘制辅助线 switch (obstacle.getShape()) { case CIRCLE: drawCircleEditingGuide(g2d, xyCoords, isDragging, dragPointIndex); break; case POLYGON: drawPolygonEditingGuide(g2d, xyCoords, isDragging, dragPointIndex); break; } } /** * 绘制圆形障碍物编辑模式下的辅助线 */ private static void drawCircleEditingGuide(Graphics2D g2d, List xyCoords, boolean isDragging, int dragPointIndex) { if (xyCoords.size() < 2) { return; } Obstacledge.XYCoordinate center = xyCoords.get(0); Obstacledge.XYCoordinate pointOnCircle = xyCoords.get(1); // 绘制半径线 g2d.drawLine((int)center.getX(), (int)center.getY(), (int)pointOnCircle.getX(), (int)pointOnCircle.getY()); // 绘制中心十字线 int crossSize = 5; g2d.drawLine((int)center.getX() - crossSize, (int)center.getY(), (int)center.getX() + crossSize, (int)center.getY()); g2d.drawLine((int)center.getX(), (int)center.getY() - crossSize, (int)center.getX(), (int)center.getY() + crossSize); // 如果正在拖拽控制点,绘制拖拽线 if (isDragging && dragPointIndex >= 0 && dragPointIndex < xyCoords.size()) { g2d.setColor(Color.RED); g2d.setStroke(new BasicStroke(2.0f)); Obstacledge.XYCoordinate draggedPoint = xyCoords.get(dragPointIndex); // 绘制从控制点到鼠标的连线 // 这里需要外部传入鼠标位置,暂时不实现 } } /** * 绘制多边形障碍物编辑模式下的辅助线 */ private static void drawPolygonEditingGuide(Graphics2D g2d, List xyCoords, boolean isDragging, int dragPointIndex) { if (xyCoords.size() < 3) { return; } // 绘制顶点之间的辅助线 int n = xyCoords.size(); for (int i = 0; i < n; i++) { Obstacledge.XYCoordinate current = xyCoords.get(i); Obstacledge.XYCoordinate next = xyCoords.get((i + 1) % n); g2d.drawLine((int)current.getX(), (int)current.getY(), (int)next.getX(), (int)next.getY()); // 绘制每个顶点的十字线 int crossSize = 3; g2d.drawLine((int)current.getX() - crossSize, (int)current.getY(), (int)current.getX() + crossSize, (int)current.getY()); g2d.drawLine((int)current.getX(), (int)current.getY() - crossSize, (int)current.getX(), (int)current.getY() + crossSize); } // 如果正在拖拽控制点,绘制拖拽线 if (isDragging && dragPointIndex >= 0 && dragPointIndex < xyCoords.size()) { g2d.setColor(Color.RED); g2d.setStroke(new BasicStroke(2.0f)); Obstacledge.XYCoordinate draggedPoint = xyCoords.get(dragPointIndex); // 绘制从控制点到鼠标的连线 // 这里需要外部传入鼠标位置,暂时不实现 } } }