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 final Image mowerIcon; private final Ellipse2D.Double fallbackShape = new Ellipse2D.Double(); private Point2D.Double position = new Point2D.Double(); private boolean positionValid; private double headingDegrees; public Gecaoji() { mowerIcon = loadIcon(); refreshFromDevice(); } private Image loadIcon() { try { ImageIcon icon = new ImageIcon("image/gecaoji.png"); if (icon.getIconWidth() <= 0 || icon.getIconHeight() <= 0) { return null; } return icon.getImage(); } catch (Exception ex) { System.err.println("Unable to load mower icon: " + ex.getMessage()); return null; } } public void refreshFromDevice() { 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)) { positionValid = false; return; } ensurePosition(); position.x = x; position.y = y; positionValid = true; headingDegrees = heading; } 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); if (mowerIcon != null && mowerIcon.getWidth(null) > 0 && mowerIcon.getHeight(null) > 0) { drawIcon(g2d, worldSize); } else { drawFallback(g2d, worldSize); } } private void drawIcon(Graphics2D g2d, double worldSize) { double iconWidth = mowerIcon.getWidth(null); double iconHeight = mowerIcon.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(mowerIcon, 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); } 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; } }