package gecaoji; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.geom.Ellipse2D; import java.awt.geom.Point2D; import javax.swing.ImageIcon; /** Mower rendering helper. */ public class Gecaoji { private static final double ICON_PIXEL_SIZE = 48.0; private static final double ICON_SCALE_FACTOR = 0.8; private static final double MIN_SCALE = 1e-6; private static final Color FALLBACK_FILL = new Color(0, 150, 0); private static final String DEFAULT_ICON_PATH = "image/gecaoji.png"; private static final String HANDHELD_ICON_PATH = "image/URT.png"; private final Image defaultIcon; private final Image handheldIcon; private Image activeIcon; private boolean handheldIconActive; private final Ellipse2D.Double fallbackShape = new Ellipse2D.Double(); private Point2D.Double position = new Point2D.Double(); private boolean positionValid; private double headingDegrees; public Gecaoji() { defaultIcon = loadIcon(DEFAULT_ICON_PATH); handheldIcon = loadIcon(HANDHELD_ICON_PATH); activeIcon = defaultIcon != null ? defaultIcon : handheldIcon; handheldIconActive = false; refreshFromDevice(); } private Image loadIcon(String path) { try { ImageIcon icon = new ImageIcon(path); if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) { return null; } return icon.getImage(); } catch (Exception ex) { System.err.println("Unable to load mower icon from " + path + ": " + ex.getMessage()); return null; } } public void refreshFromDevice() { // 检查是否正在导航预览模式,如果是则不更新位置 if (isNavigating()) { return; } Device device = Device.getGecaoji(); if (device == null) { positionValid = false; return; } double x = parseCoordinate(device.getRealtimeX()); double y = parseCoordinate(device.getRealtimeY()); double heading = parseHeading(device.getHeading()); if (Double.isNaN(x) || Double.isNaN(y)) { // Keep showing the last known mower position when temporary sensor glitches occur. return; } ensurePosition(); position.x = x; position.y = y; positionValid = true; headingDegrees = heading; } /** * 检查是否正在导航预览模式 * @return 如果正在导航预览返回true,否则返回false */ private boolean isNavigating() { try { dikuai.daohangyulan nav = dikuai.daohangyulan.getInstance(); if (nav != null) { return nav.isNavigating(); } } catch (Exception e) { // 如果获取导航实例失败,返回false(不影响主要功能) } return false; } private void ensurePosition() { if (position == null) { position = new Point2D.Double(); } } private double parseCoordinate(String value) { if (value == null) { return Double.NaN; } String trimmed = value.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return Double.NaN; } try { return Double.parseDouble(trimmed); } catch (NumberFormatException ex) { return Double.NaN; } } private double parseHeading(String value) { if (value == null) { return 0.0; } String trimmed = value.trim(); if (trimmed.isEmpty() || "-1".equals(trimmed)) { return 0.0; } try { double parsed = Double.parseDouble(trimmed); if (!Double.isFinite(parsed)) { return 0.0; } double normalized = parsed % 360.0; return normalized < 0 ? normalized + 360.0 : normalized; } catch (NumberFormatException ex) { return 0.0; } } public void draw(Graphics2D g2d, double scale) { if (!positionValid) { return; } double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE); Image icon = activeIcon; if (icon != null && icon.getWidth(null) > 0 && icon.getHeight(null) > 0) { drawIcon(g2d, worldSize, icon); } else { drawFallback(g2d, worldSize); } } private void drawIcon(Graphics2D g2d, double worldSize, Image icon) { double iconWidth = icon.getWidth(null); double iconHeight = icon.getHeight(null); double maxSide = Math.max(iconWidth, iconHeight); double scaleFactor = worldSize / Math.max(maxSide, MIN_SCALE); double rotationRadians = Math.toRadians(-headingDegrees); AffineTransform original = g2d.getTransform(); AffineTransform transformed = new AffineTransform(original); transformed.translate(position.x, position.y); if (rotationRadians != 0.0) { transformed.rotate(rotationRadians); } transformed.scale(scaleFactor, scaleFactor); transformed.translate(-iconWidth / 2.0, -iconHeight / 2.0); g2d.setTransform(transformed); g2d.drawImage(icon, 0, 0, null); g2d.setTransform(original); } private void drawFallback(Graphics2D g2d, double worldSize) { double radius = worldSize / 2.0; ensurePosition(); fallbackShape.setFrame(position.x - radius, position.y - radius, worldSize, worldSize); Color original = g2d.getColor(); g2d.setColor(FALLBACK_FILL); g2d.fill(fallbackShape); g2d.setColor(Color.WHITE); g2d.draw(fallbackShape); double rotationRadians = Math.toRadians(-headingDegrees); double lineLength = radius; double dx = lineLength * Math.sin(rotationRadians); double dy = lineLength * Math.cos(rotationRadians); g2d.drawLine( (int) Math.round(position.x), (int) Math.round(position.y), (int) Math.round(position.x + dx), (int) Math.round(position.y + dy)); g2d.setColor(original); } public boolean hasValidPosition() { return positionValid; } public Point2D.Double getPosition() { if (!positionValid) { return null; } ensurePosition(); return new Point2D.Double(position.x, position.y); } /** * 设置割草机位置(用于导航预览等场景) * @param x X坐标 * @param y Y坐标 */ public void setPosition(double x, double y) { ensurePosition(); position.x = x; position.y = y; positionValid = true; } /** * 设置割草机方向(用于导航预览等场景) * @param headingDegrees 方向角度(度,0-360) */ public void setHeading(double headingDegrees) { double normalized = headingDegrees % 360.0; if (normalized < 0) { normalized += 360.0; } this.headingDegrees = normalized; } /** * 获取割草机方向 * @return 方向角度(度,0-360) */ public double getHeading() { return headingDegrees; } public double getWorldRadius(double scale) { if (!positionValid) { return Double.NaN; } double worldSize = (ICON_PIXEL_SIZE * ICON_SCALE_FACTOR) / Math.max(scale, MIN_SCALE); return worldSize / 2.0; } public boolean useHandheldIcon(boolean handheldMode) { if (handheldIconActive == handheldMode) { return false; } handheldIconActive = handheldMode; Image next = handheldMode ? (handheldIcon != null ? handheldIcon : defaultIcon) : defaultIcon; if (next == null) { next = handheldIcon; } activeIcon = next; return true; } }