diff --git a/modules/common/src/main/java/edu/tigers/sumatra/drawable/DrawableBotPattern.java b/modules/common/src/main/java/edu/tigers/sumatra/drawable/DrawableBotPattern.java index d08da81e..a93af1aa 100644 --- a/modules/common/src/main/java/edu/tigers/sumatra/drawable/DrawableBotPattern.java +++ b/modules/common/src/main/java/edu/tigers/sumatra/drawable/DrawableBotPattern.java @@ -5,6 +5,7 @@ package edu.tigers.sumatra.drawable; import edu.tigers.sumatra.ids.BotID; +import edu.tigers.sumatra.ids.ETeamColor; import edu.tigers.sumatra.math.vector.IVector2; import edu.tigers.sumatra.math.vector.Vector2; @@ -108,8 +109,8 @@ public DrawableBotPattern( super(pos, angle, radius, center2DribblerDist); this.botID = botID; - setBorderColor(null); - setFillColor(Color.black); + setBorderColor(botID.getTeamColor() == ETeamColor.YELLOW ? Color.white : Color.black); + setFillColor(botID.getTeamColor() == ETeamColor.YELLOW ? Color.darkGray : Color.black); setDrawDirection(false); } diff --git a/modules/moduli-wp/src/main/java/edu/tigers/sumatra/wp/vis/BorderVisCalc.java b/modules/moduli-wp/src/main/java/edu/tigers/sumatra/wp/vis/BorderVisCalc.java index f00b7e18..ae013311 100644 --- a/modules/moduli-wp/src/main/java/edu/tigers/sumatra/wp/vis/BorderVisCalc.java +++ b/modules/moduli-wp/src/main/java/edu/tigers/sumatra/wp/vis/BorderVisCalc.java @@ -73,7 +73,7 @@ public void process(final WorldFrameWrapper wfw, final ShapeMap shapeMap) private void drawLine(List list, IDrawableShape newShape) { - list.add(newShape.setColor(Color.WHITE).setStrokeWidth(Geometry.getLineWidth())); + list.add(newShape.setColor(Color.WHITE).setStrokeWidth(Geometry.getLineWidth() * 2)); } diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/EFieldPanelShapeLayer.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/EFieldPanelShapeLayer.java index 89a43c0b..162cd9a1 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/EFieldPanelShapeLayer.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/EFieldPanelShapeLayer.java @@ -24,4 +24,6 @@ public final class EFieldPanelShapeLayer F.category(PANEL).layerName("Ruler").visibleByDefault(true)); public static final IShapeLayerIdentifier RECORDING = F.create( F.category(PANEL).layerName("Recording").visibleByDefault(true)); + public static final IShapeLayerIdentifier SELECTION = F.create( + F.category(PANEL).layerName("Selection").visibleByDefault(true)); } diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/ISelectedRobotsChanged.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/ISelectedRobotsChanged.java new file mode 100644 index 00000000..c981376e --- /dev/null +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/ISelectedRobotsChanged.java @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2009 - 2024, DHBW Mannheim - TIGERs Mannheim + */ + +package edu.tigers.sumatra.visualizer.field; + +import edu.tigers.sumatra.ids.BotID; + +import java.util.List; + + +public interface ISelectedRobotsChanged +{ + void selectedRobotsChanged(List selectedBots); +} diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/VisualizerFieldPresenter.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/VisualizerFieldPresenter.java index d8569019..701288ea 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/VisualizerFieldPresenter.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/VisualizerFieldPresenter.java @@ -5,32 +5,48 @@ package edu.tigers.sumatra.visualizer.field; import edu.tigers.sumatra.clock.FpsCounter; +import edu.tigers.sumatra.drawable.DrawableCircle; import edu.tigers.sumatra.drawable.DrawableFieldBackground; +import edu.tigers.sumatra.drawable.DrawableLine; +import edu.tigers.sumatra.drawable.DrawableRectangle; import edu.tigers.sumatra.drawable.EFieldTurn; import edu.tigers.sumatra.drawable.ShapeMap; import edu.tigers.sumatra.drawable.ShapeMapSource; +import edu.tigers.sumatra.geometry.Geometry; +import edu.tigers.sumatra.ids.BotID; +import edu.tigers.sumatra.math.circle.Circle; +import edu.tigers.sumatra.math.line.ILineSegment; +import edu.tigers.sumatra.math.rectangle.Rectangle; import edu.tigers.sumatra.math.vector.IVector2; +import edu.tigers.sumatra.math.vector.Vector2; import edu.tigers.sumatra.views.ISumatraPresenter; import edu.tigers.sumatra.visualizer.field.components.CoordinatesMouseAdapter; import edu.tigers.sumatra.visualizer.field.components.DragMouseAdapter; import edu.tigers.sumatra.visualizer.field.components.DrawableCoordinates; import edu.tigers.sumatra.visualizer.field.components.DrawableFps; import edu.tigers.sumatra.visualizer.field.components.DrawableRuler; +import edu.tigers.sumatra.visualizer.field.components.RobotPositionerMouseAdapter; import edu.tigers.sumatra.visualizer.field.components.RulerMouseAdapter; +import edu.tigers.sumatra.visualizer.field.components.SelectionRectangleMouseAdapter; import edu.tigers.sumatra.visualizer.field.components.ZoomMouseAdapter; import edu.tigers.sumatra.visualizer.field.recorder.DrawableRecordingAnimation; import edu.tigers.sumatra.wp.IWorldFrameObserver; +import edu.tigers.sumatra.wp.data.ITrackedBot; +import edu.tigers.sumatra.wp.data.WorldFrameWrapper; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; import lombok.extern.log4j.Log4j2; +import javax.swing.SwingUtilities; +import java.awt.Color; import java.awt.Graphics2D; import java.awt.Image; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -58,6 +74,22 @@ public class VisualizerFieldPresenter implements ISumatraPresenter, IWorldFrameO @Setter private DrawableRuler drawableRuler = null; + + @Setter + private DrawableRectangle drawableSelectionBox = null; + @Setter + private Rectangle selectionBox = null; + + @Setter + private ILineSegment robotPositionerLine = null; + @Setter + private DrawableLine drawableRobotPositionerLine = null; + + private Map bots = new HashMap<>(); + + @Setter + private List selectedBots = new ArrayList<>(); + @Setter private List coordinates = List.of(); private final FpsCounter fpsCounter = new FpsCounter(); @@ -65,10 +97,15 @@ public class VisualizerFieldPresenter implements ISumatraPresenter, IWorldFrameO @Setter private DrawableRecordingAnimation drawableRecordingAnimation; + @Setter + private ISelectedRobotsChanged selectedRobotsChangedListener; + @Getter private final List onFieldClicks = new ArrayList<>(); @Getter private final List onMouseMoves = new ArrayList<>(); + @Getter + private final List onRobotMove = new ArrayList<>(); private List mouseAdapters = List.of(); @@ -85,7 +122,11 @@ public void onStart() new RulerMouseAdapter(this::getMousePointGlobal, this::setDrawableRuler), new ZoomMouseAdapter(fieldPane::scale), new DragMouseAdapter(fieldPane::drag), - new InteractionMouseEvents() + new SelectionRectangleMouseAdapter(this::getMousePointGlobal, this::setDrawableSelectionBox, + this::setSelectionBox), + new InteractionMouseEvents(), + new RobotPositionerMouseAdapter(this::getMousePointGlobal, this::setDrawableRobotPositionerLine, + this::setRobotPositionerLine) ); mouseAdapters.forEach(fieldPanel::addMouseAdapter); fieldPanel.setVisible(true); @@ -105,6 +146,13 @@ public void onStop() } + @Override + public void onNewWorldFrame(WorldFrameWrapper wFrameWrapper) + { + this.bots = wFrameWrapper.getSimpleWorldFrame().getBots(); + } + + @Override public void onNewShapeMap(final long timestamp, final ShapeMap shapeMap, final ShapeMapSource source) { @@ -224,6 +272,54 @@ private void updateInternalShapeLayers() panelShapeMap.get(EFieldPanelShapeLayer.COORDINATES).clear(); panelShapeMap.get(EFieldPanelShapeLayer.COORDINATES).addAll(coordinates); + panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).clear(); + Optional.ofNullable(drawableSelectionBox) + .ifPresent(box -> panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).add(box)); + + Optional.ofNullable(drawableRobotPositionerLine) + .ifPresent(line -> panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).add(line)); + + if (selectionBox != null) + { + selectedBots.clear(); + bots.values() + .stream() + .filter(bot -> selectionBox.isPointInShape(bot.getPos())) + .forEach(bot -> + { + panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).add(new DrawableCircle( + Circle.createCircle(bot.getPos(), Geometry.getBotRadius() + 4)) + .setColor(new Color(255, 180, 0, 255))); + selectedBots.add(bot.getBotId()); + }); + selectedRobotsChangedListener.selectedRobotsChanged(selectedBots); + } else if (bots != null) + { + bots.values() + .stream() + .filter(bot -> selectedBots.contains(bot.getBotId())) + .forEach(bot -> panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).add(new DrawableCircle( + Circle.createCircle(bot.getPos(), Geometry.getBotRadius() + 4)) + .setColor(new Color(0, 225, 255, 255)))); + } + + if (robotPositionerLine != null && selectedBots != null && !selectedBots.isEmpty()) + { + int numBots = selectedBots.size(); + var start = robotPositionerLine.getPathStart(); + var end = robotPositionerLine.getPathEnd(); + var startToEnd = end.subtractNew(start); + + double step = getStepSize(startToEnd, numBots); + for (int i = 0; i < numBots; i++) + { + var pos = start.addNew(startToEnd.scaleToNew(step * i)); + panelShapeMap.get(EFieldPanelShapeLayer.SELECTION).add(new DrawableCircle( + Circle.createCircle(pos, Geometry.getBotRadius() + 4)) + .setColor(new Color(0, 225, 255, 255))); + } + } + panelShapeMap.get(EFieldPanelShapeLayer.RULER).clear(); Optional.ofNullable(drawableRuler) .ifPresent(ruler -> panelShapeMap.get(EFieldPanelShapeLayer.RULER).add(ruler)); @@ -265,7 +361,35 @@ private class InteractionMouseEvents extends MouseAdapter public void mouseClicked(final MouseEvent e) { IVector2 globalPos = getMousePointGlobal(e.getX(), e.getY()); - onFieldClicks.forEach(c -> c.onInteraction(globalPos, e)); + if (e.isControlDown()) + { + selectedBots.clear(); + } else + { + onFieldClicks.forEach(c -> c.onInteraction(globalPos, e)); + } + } + + + @Override + public void mouseReleased(MouseEvent e) + { + if (e.isControlDown() && SwingUtilities.isRightMouseButton(e) && robotPositionerLine != null + && selectedBots != null && !selectedBots.isEmpty()) + { + var start = robotPositionerLine.getPathStart(); + var end = robotPositionerLine.getPathEnd(); + var startToEnd = end.subtractNew(start); + int numBots = selectedBots.size(); + + double step = getStepSize(startToEnd, numBots); + for (int i = 0; i < numBots; i++) + { + var pos = start.addNew(startToEnd.scaleToNew(step * i)); + final BotID botId = selectedBots.get(i); + onRobotMove.forEach(c -> c.onInteraction(botId, pos)); + } + } } @@ -274,9 +398,29 @@ public void mouseMoved(final MouseEvent e) { IVector2 lastMousePoint = getMousePointGlobal(e.getX(), e.getY()); onMouseMoves.forEach(c -> c.onInteraction(lastMousePoint, e)); + update(); } } + + private static double getStepSize(Vector2 startToEnd, int numBots) + { + var distance = startToEnd.getLength(); + double step = distance / 2; + if (numBots > 1) + { + step = distance / (numBots - 1); + } + return step; + } + + + @FunctionalInterface + public interface FieldRobotMoveInteraction + { + void onInteraction(BotID id, IVector2 pos); + } + @FunctionalInterface public interface FieldMouseInteraction { diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RobotPositionerMouseAdapter.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RobotPositionerMouseAdapter.java new file mode 100644 index 00000000..fde2f3dd --- /dev/null +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RobotPositionerMouseAdapter.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2009 - 2022, DHBW Mannheim - TIGERs Mannheim + */ + +package edu.tigers.sumatra.visualizer.field.components; + +import edu.tigers.sumatra.drawable.DrawableLine; +import edu.tigers.sumatra.math.line.ILineSegment; +import edu.tigers.sumatra.math.line.Lines; +import edu.tigers.sumatra.math.vector.IVector2; +import edu.tigers.sumatra.visualizer.field.callbacks.MousePointTransformer; +import lombok.RequiredArgsConstructor; + +import javax.swing.SwingUtilities; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; + + +@RequiredArgsConstructor +public class RobotPositionerMouseAdapter extends MouseAdapter +{ + private final MousePointTransformer mousePointTransformer; + private final Consumer drawableLineConsumer; + private final Consumer lineConsumer; + private IVector2 dragPointStart; + + @Override + public void mousePressed(final MouseEvent e) + { + dragPointStart = mousePointTransformer.toGlobal(e.getX(), e.getY()); + } + + + @Override + public void mouseDragged(final MouseEvent e) + { + if (SwingUtilities.isRightMouseButton(e) && (e.isControlDown()) && dragPointStart != null) + { + IVector2 dragPointEnd = mousePointTransformer.toGlobal(e.getX(), e.getY()); + ILineSegment line = Lines.segmentFromPoints(dragPointStart, dragPointEnd); + DrawableLine drawableLine = new DrawableLine(line); + drawableLineConsumer.accept(drawableLine); + lineConsumer.accept(line); + } + } + + + @Override + public void mouseReleased(final MouseEvent e) + { + dragPointStart = null; + drawableLineConsumer.accept(null); + lineConsumer.accept(null); + } +} diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RulerMouseAdapter.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RulerMouseAdapter.java index 0c71b34a..0381145d 100644 --- a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RulerMouseAdapter.java +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/RulerMouseAdapter.java @@ -31,7 +31,7 @@ public void mousePressed(final MouseEvent e) @Override public void mouseDragged(final MouseEvent e) { - if (SwingUtilities.isLeftMouseButton(e) && (e.isControlDown() || e.isAltDown()) && dragPointStart != null) + if (SwingUtilities.isLeftMouseButton(e) && (e.isAltDown()) && dragPointStart != null) { IVector2 dragPointEnd = mousePointTransformer.toGlobal(e.getX(), e.getY()); DrawableRuler ruler = new DrawableRuler(dragPointStart, dragPointEnd); diff --git a/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/SelectionRectangleMouseAdapter.java b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/SelectionRectangleMouseAdapter.java new file mode 100644 index 00000000..ad94bdf1 --- /dev/null +++ b/modules/sumatra-gui-visualizer/src/main/java/edu/tigers/sumatra/visualizer/field/components/SelectionRectangleMouseAdapter.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2009 - 2022, DHBW Mannheim - TIGERs Mannheim + */ + +package edu.tigers.sumatra.visualizer.field.components; + +import edu.tigers.sumatra.drawable.DrawableRectangle; +import edu.tigers.sumatra.math.rectangle.Rectangle; +import edu.tigers.sumatra.math.vector.IVector2; +import edu.tigers.sumatra.visualizer.field.callbacks.MousePointTransformer; +import lombok.RequiredArgsConstructor; + +import javax.swing.SwingUtilities; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.function.Consumer; + + +@RequiredArgsConstructor +public class SelectionRectangleMouseAdapter extends MouseAdapter +{ + private final MousePointTransformer mousePointTransformer; + private final Consumer rectangleConsumer; + private final Consumer selectionBoxConsumer; + private IVector2 dragPointStart; + + @Override + public void mousePressed(final MouseEvent e) + { + dragPointStart = mousePointTransformer.toGlobal(e.getX(), e.getY()); + } + + + @Override + public void mouseDragged(final MouseEvent e) + { + if (SwingUtilities.isLeftMouseButton(e) && (e.isControlDown()) && dragPointStart != null) + { + IVector2 dragPointEnd = mousePointTransformer.toGlobal(e.getX(), e.getY()); + Rectangle rectangle = Rectangle.fromPoints(dragPointStart, dragPointEnd); + DrawableRectangle drawableRectangle = new DrawableRectangle(rectangle); + rectangleConsumer.accept(drawableRectangle); + selectionBoxConsumer.accept(rectangle); + } + } + + + @Override + public void mouseReleased(final MouseEvent e) + { + dragPointStart = null; + rectangleConsumer.accept(null); + selectionBoxConsumer.accept(null); + } +}