From c82cdc96d5201e8c3f7e51409a0713110e7ed31f Mon Sep 17 00:00:00 2001
From: "A. Teo Welton" <76081718+DragonDev07@users.noreply.github.com>
Date: Wed, 6 Nov 2024 13:34:48 -0700
Subject: [PATCH 1/2] Exporting paths functioning
---
.idea/caches/deviceStreaming.xml | 33 +-
.../org/rowlandhall/meepmeep/MeepMeep.kt | 620 ++++++++++++++----
.../meepmeep/core/entity/BotEntity.kt | 1 -
.../entity/TrajectorySequenceEntity.kt | 139 ++--
.../TrajectorySequenceBuilder.java | 1 +
5 files changed, 577 insertions(+), 217 deletions(-)
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index af74dbf..5a2f866 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -25,6 +25,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -245,6 +256,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -301,17 +323,6 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt b/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
index 8eee087..308b814 100644
--- a/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
+++ b/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
@@ -9,12 +9,20 @@ import org.rowlandhall.meepmeep.core.entity.Entity
import org.rowlandhall.meepmeep.core.entity.EntityEventListener
import org.rowlandhall.meepmeep.core.entity.ThemedEntity
import org.rowlandhall.meepmeep.core.entity.ZIndexManager
+import org.rowlandhall.meepmeep.core.scaleInToPixel
+import org.rowlandhall.meepmeep.core.toDegrees
+import org.rowlandhall.meepmeep.core.toRadians
+import org.rowlandhall.meepmeep.core.toScreenCoord
import org.rowlandhall.meepmeep.core.ui.WindowFrame
import org.rowlandhall.meepmeep.core.util.FieldUtil
import org.rowlandhall.meepmeep.core.util.LoopManager
import org.rowlandhall.meepmeep.roadrunner.entity.RoadRunnerBotEntity
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TrajectorySegment
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TurnSegment
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.WaitSegment
import org.rowlandhall.meepmeep.roadrunner.ui.TrajectoryProgressSliderMaster
import java.awt.AlphaComposite
+import java.awt.BasicStroke
import java.awt.Font
import java.awt.Graphics2D
import java.awt.Image
@@ -26,9 +34,14 @@ import java.awt.event.KeyListener
import java.awt.event.MouseEvent
import java.awt.event.MouseListener
import java.awt.event.MouseMotionListener
+import java.awt.geom.Path2D
import java.awt.image.BufferedImage
+import java.io.File
import javax.imageio.ImageIO
import javax.swing.UIManager
+import kotlin.math.abs
+import kotlin.math.atan2
+import kotlin.math.min
/**
* The [MeepMeep] class is the main entry point for the Meep Meep
@@ -36,8 +49,14 @@ import javax.swing.UIManager
* the application window, rendering, and entity management.
*
* @constructor Creates a [MeepMeep] instance with specified window
+ *
+ * ```
* dimensions and optional fps.
- * @property windowX The width of the application window.
+ * @property windowX
+ * ```
+ *
+ * The width of the application window.
+ *
* @property windowY The height of the application window.
* @property fps The frames per second for the application loop.
* @see [WindowFrame]
@@ -50,9 +69,9 @@ import javax.swing.UIManager
* @see [FieldUtil]
*/
@Suppress("unused", "MemberVisibilityCanBePrivate", "SpellCheckingInspection")
-class MeepMeep @JvmOverloads constructor(
- private val windowX: Int, private val windowY: Int, private val fps: Int = 60
-) {
+class MeepMeep
+@JvmOverloads
+constructor(private val windowX: Int, private val windowY: Int, private val fps: Int = 60) {
/**
* Companion object to hold default entities and fonts used in the MeepMeep
* application.
@@ -134,9 +153,7 @@ class MeepMeep @JvmOverloads constructor(
*/
private val progressSliderMasterPanel: TrajectoryProgressSliderMaster by lazy {
// Create a new instance of TrajectoryProgressSliderMaster
- TrajectoryProgressSliderMaster(
- this, FieldUtil.CANVAS_WIDTH.toInt(), 20
- )
+ TrajectoryProgressSliderMaster(this, FieldUtil.CANVAS_WIDTH.toInt(), 20)
}
// TODO: Make custom dirty list that auto sorts
@@ -148,19 +165,25 @@ class MeepMeep @JvmOverloads constructor(
val classLoader = Thread.currentThread().contextClassLoader
// Load Roboto Regular font from file
- FONT_ROBOTO_REGULAR = Font.createFont(
- Font.TRUETYPE_FONT, classLoader.getResourceAsStream("font/Roboto-Regular.ttf")
- )
+ FONT_ROBOTO_REGULAR =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ classLoader.getResourceAsStream("font/Roboto-Regular.ttf")
+ )
// Load Roboto Bold font from file
- FONT_ROBOTO_BOLD = Font.createFont(
- Font.TRUETYPE_FONT, classLoader.getResourceAsStream("font/Roboto-Bold.ttf")
- )
+ FONT_ROBOTO_BOLD =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ classLoader.getResourceAsStream("font/Roboto-Bold.ttf")
+ )
// Load Roboto Bold Italic font from file
- FONT_ROBOTO_BOLD_ITALIC = Font.createFont(
- Font.TRUETYPE_FONT, classLoader.getResourceAsStream("font/Roboto-BoldItalic.ttf")
- )
+ FONT_ROBOTO_BOLD_ITALIC =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ classLoader.getResourceAsStream("font/Roboto-BoldItalic.ttf")
+ )
// Set the look and feel of the UI to the system's default
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())
@@ -176,14 +199,12 @@ class MeepMeep @JvmOverloads constructor(
FieldUtil.CANVAS_HEIGHT = windowY.toDouble()
// Initialize axes entity
- DEFAULT_AXES_ENTITY = AxesEntity(
- this, 0.8, colorManager.theme, FONT_ROBOTO_BOLD_ITALIC, 20f
- )
+ DEFAULT_AXES_ENTITY =
+ AxesEntity(this, 0.8, colorManager.theme, FONT_ROBOTO_BOLD_ITALIC, 20f)
// Initialize compass entity
- DEFAULT_COMPASS_ENTITY = CompassEntity(
- this, colorManager.theme, 30.0, 30.0, Vector2d(-54.0, 54.0)
- )
+ DEFAULT_COMPASS_ENTITY =
+ CompassEntity(this, colorManager.theme, 30.0, 30.0, Vector2d(-54.0, 54.0))
// Add the progress slider panel to the canvas panel
windowFrame.canvasPanel.add(progressSliderMasterPanel)
@@ -192,61 +213,72 @@ class MeepMeep @JvmOverloads constructor(
windowFrame.pack()
// Add mouse motion listener to the canvas
- canvas.addMouseMotionListener(object: MouseMotionListener {
- override fun mouseDragged(p0: MouseEvent?) {}
+ canvas.addMouseMotionListener(
+ object: MouseMotionListener {
+ override fun mouseDragged(p0: MouseEvent?) {}
- override fun mouseMoved(e: MouseEvent) {
- canvasMouseX = e.x
- canvasMouseY = e.y
+ override fun mouseMoved(e: MouseEvent) {
+ canvasMouseX = e.x
+ canvasMouseY = e.y
+ }
}
- })
+ )
// Add key listener to the canvas
- canvas.addKeyListener(object: KeyListener {
- /**
- * Invoked when a key has been typed. This event occurs when a key press is
- * followed by a key release.
- *
- * @param p0 The KeyEvent that triggered this method.
- */
- override fun keyTyped(p0: KeyEvent?) {}
-
- /**
- * Invoked when a key has been pressed. This event occurs when a key press
- * is detected.
- *
- * @param e The KeyEvent that triggered this method.
- */
- override fun keyPressed(e: KeyEvent) { // Check if the 'C' or 'COPY' (Often `Ctrl/CMD + C`) key is pressed
- if (e.keyCode == KeyEvent.VK_C || e.keyCode == KeyEvent.VK_COPY) { // Convert mouse coordinates from screen to field coordinates
- val mouseToFieldCoords = FieldUtil.screenCoordsToFieldCoords(
- Vector2d(
- canvasMouseX.toDouble(), canvasMouseY.toDouble()
- )
- )
-
- // Format the coordinates as a string
- val stringSelection = StringSelection(
- "%.1f, %.1f".format(
- mouseToFieldCoords.x,
- mouseToFieldCoords.y,
- )
- )
-
- // Get the system clipboard and set the contents to the formatted coordinates
- val clipboard = Toolkit.getDefaultToolkit().systemClipboard
- clipboard.setContents(stringSelection, null)
+ canvas.addKeyListener(
+ object: KeyListener {
+ /**
+ * Invoked when a key has been typed. This event occurs when a key press is
+ * followed by a key release.
+ *
+ * @param p0 The KeyEvent that triggered this method.
+ */
+ override fun keyTyped(p0: KeyEvent?) {}
+
+ /**
+ * Invoked when a key has been pressed. This event occurs when a key press
+ * is detected.
+ *
+ * @param e The KeyEvent that triggered this method.
+ */
+ override fun keyPressed(
+ e: KeyEvent
+ ) { // Check if the 'C' or 'COPY' (Often `Ctrl/CMD + C`) key is pressed
+ if (e.keyCode == KeyEvent.VK_C || e.keyCode == KeyEvent.VK_COPY
+ ) { // Convert mouse coordinates from screen to field coordinates
+ val mouseToFieldCoords =
+ FieldUtil.screenCoordsToFieldCoords(
+ Vector2d(
+ canvasMouseX.toDouble(),
+ canvasMouseY.toDouble()
+ )
+ )
+
+ // Format the coordinates as a string
+ val stringSelection =
+ StringSelection(
+ "%.1f, %.1f".format(
+ mouseToFieldCoords.x,
+ mouseToFieldCoords.y,
+ )
+ )
+
+ // Get the system clipboard and set the contents to the formatted
+ // coordinates
+ val clipboard = Toolkit.getDefaultToolkit().systemClipboard
+ clipboard.setContents(stringSelection, null)
+ }
}
- }
- /**
- * Invoked when a key has been released. This event occurs when a key
- * release is detected.
- *
- * @param p0 The KeyEvent that triggered this method.
- */
- override fun keyReleased(p0: KeyEvent?) {}
- })
+ /**
+ * Invoked when a key has been released. This event occurs when a key
+ * release is detected.
+ *
+ * @param p0 The KeyEvent that triggered this method.
+ */
+ override fun keyReleased(p0: KeyEvent?) {}
+ }
+ )
// Set the z-index hierarchy for entities
zIndexManager.setTagHierarchy(
@@ -310,14 +342,16 @@ class MeepMeep @JvmOverloads constructor(
}
// Convert mouse coordinates from screen to field coordinates
- val mouseToFieldCoords = FieldUtil.screenCoordsToFieldCoords(
- Vector2d(canvasMouseX.toDouble(), canvasMouseY.toDouble())
- )
+ val mouseToFieldCoords =
+ FieldUtil.screenCoordsToFieldCoords(
+ Vector2d(canvasMouseX.toDouble(), canvasMouseY.toDouble())
+ )
// Draw the mouse coordinates
g.font = FONT_ROBOTO_BOLD.deriveFont(14f)
g.color =
- if (colorManager.isDarkMode) ColorManager.COLOR_PALETTE.gray100 else ColorManager.COLOR_PALETTE.gray800
+ if (colorManager.isDarkMode) ColorManager.COLOR_PALETTE.gray100
+ else ColorManager.COLOR_PALETTE.gray800
g.drawString(
"(%.1f, %.1f)".format(mouseToFieldCoords.x, mouseToFieldCoords.y),
mouseCoordinateDisplayX,
@@ -407,35 +441,74 @@ class MeepMeep @JvmOverloads constructor(
fun setBackground(background: Background): MeepMeep {
val classLoader = Thread.currentThread().contextClassLoader
- val backgroundMap = Background.values().associateWith { bg ->
- val path = when (bg) {
- Background.GRID_BLUE -> "background/misc/field-grid-blue.jpg"
- Background.GRID_GREEN -> "background/misc/field-grid-green.jpg"
- Background.GRID_GRAY -> "background/misc/field-grid-gray.jpg"
- Background.FIELD_SKYSTONE_OFFICIAL -> "background/season-2019-skystone/field-2019-skystone-official.png"
- Background.FIELD_SKYSTONE_GF_DARK -> "background/season-2019-skystone/field-2019-skystone-gf-dark.png"
- Background.FIELD_SKYSTONE_INNOV8RZ_LIGHT -> "background/season-2019-skystone/field-2019-skystone-innov8rz-light.jpg"
- Background.FIELD_SKYSTONE_INNOV8RZ_DARK -> "background/season-2019-skystone/field-2019-skystone-innov8rz-dark.jpg"
- Background.FIELD_SKYSTONE_STARWARS_DARK -> "background/season-2019-skystone/field-2019-skystone-starwars.png"
- Background.FIELD_ULTIMATEGOAL_INNOV8RZ_DARK -> "background/season-2020-ultimategoal/field-2020-innov8rz-dark.jpg"
- Background.FIELD_FREIGHTFRENZY_OFFICIAL -> "background/season-2021-freightfrenzy/field-2021-official.png"
- Background.FIELD_FREIGHTFRENZY_ADI_DARK -> "background/season-2021-freightfrenzy/field-2021-adi-dark.png"
- Background.FIELD_POWERPLAY_OFFICIAL -> "background/season-2022-powerplay/field-2022-official.png"
- Background.FIELD_POWERPLAY_KAI_DARK -> "background/season-2022-powerplay/field-2022-kai-dark.png"
- Background.FIELD_POWERPLAY_KAI_LIGHT -> "background/season-2022-powerplay/field-2022-kai-light.png"
- Background.FIELD_CENTERSTAGE_OFFICIAL -> "background/season-2023-centerstage/field-2023-official.png"
- Background.FIELD_CENTERSTAGE_JUICE_DARK -> "background/season-2023-centerstage/field-2023-juice-dark.png"
- Background.FIELD_CENTERSTAGE_JUICE_LIGHT -> "background/season-2023-centerstage/field-2023-juice-light.png"
- Background.FIELD_INTOTHEDEEP_OFFICIAL -> "background/season-2024-intothedeep/field-2024-official.png"
- Background.FIELD_INTOTHEDEEP_JUICE_DARK -> "background/season-2024-intothedeep/field-2024-juice-dark.png"
- Background.FIELD_INTOTHEDEEP_JUICE_LIGHT -> "background/season-2024-intothedeep/field-2024-juice-light.png"
- Background.FIELD_INTOTHEDEEP_JUICE_GREYSCALE -> "background/season-2024-intothedeep/field-2024-juice-greyscale.png"
- Background.FIELD_INTOTHEDEEP_JUICE_BLACK -> "background/season-2024-intothedeep/field-2024-juice-black.png"
- }
+ val backgroundMap =
+ Background.values().associateWith { bg ->
+ val path =
+ when (bg) {
+ Background.GRID_BLUE -> "background/misc/field-grid-blue.jpg"
+ Background.GRID_GREEN -> "background/misc/field-grid-green.jpg"
+ Background.GRID_GRAY -> "background/misc/field-grid-gray.jpg"
+ Background.FIELD_SKYSTONE_OFFICIAL ->
+ "background/season-2019-skystone/field-2019-skystone-official.png"
- val isDarkMode = path.contains("dark", ignoreCase = true)
- Pair(path, isDarkMode)
- }
+ Background.FIELD_SKYSTONE_GF_DARK ->
+ "background/season-2019-skystone/field-2019-skystone-gf-dark.png"
+
+ Background.FIELD_SKYSTONE_INNOV8RZ_LIGHT ->
+ "background/season-2019-skystone/field-2019-skystone-innov8rz-light.jpg"
+
+ Background.FIELD_SKYSTONE_INNOV8RZ_DARK ->
+ "background/season-2019-skystone/field-2019-skystone-innov8rz-dark.jpg"
+
+ Background.FIELD_SKYSTONE_STARWARS_DARK ->
+ "background/season-2019-skystone/field-2019-skystone-starwars.png"
+
+ Background.FIELD_ULTIMATEGOAL_INNOV8RZ_DARK ->
+ "background/season-2020-ultimategoal/field-2020-innov8rz-dark.jpg"
+
+ Background.FIELD_FREIGHTFRENZY_OFFICIAL ->
+ "background/season-2021-freightfrenzy/field-2021-official.png"
+
+ Background.FIELD_FREIGHTFRENZY_ADI_DARK ->
+ "background/season-2021-freightfrenzy/field-2021-adi-dark.png"
+
+ Background.FIELD_POWERPLAY_OFFICIAL ->
+ "background/season-2022-powerplay/field-2022-official.png"
+
+ Background.FIELD_POWERPLAY_KAI_DARK ->
+ "background/season-2022-powerplay/field-2022-kai-dark.png"
+
+ Background.FIELD_POWERPLAY_KAI_LIGHT ->
+ "background/season-2022-powerplay/field-2022-kai-light.png"
+
+ Background.FIELD_CENTERSTAGE_OFFICIAL ->
+ "background/season-2023-centerstage/field-2023-official.png"
+
+ Background.FIELD_CENTERSTAGE_JUICE_DARK ->
+ "background/season-2023-centerstage/field-2023-juice-dark.png"
+
+ Background.FIELD_CENTERSTAGE_JUICE_LIGHT ->
+ "background/season-2023-centerstage/field-2023-juice-light.png"
+
+ Background.FIELD_INTOTHEDEEP_OFFICIAL ->
+ "background/season-2024-intothedeep/field-2024-official.png"
+
+ Background.FIELD_INTOTHEDEEP_JUICE_DARK ->
+ "background/season-2024-intothedeep/field-2024-juice-dark.png"
+
+ Background.FIELD_INTOTHEDEEP_JUICE_LIGHT ->
+ "background/season-2024-intothedeep/field-2024-juice-light.png"
+
+ Background.FIELD_INTOTHEDEEP_JUICE_GREYSCALE ->
+ "background/season-2024-intothedeep/field-2024-juice-greyscale.png"
+
+ Background.FIELD_INTOTHEDEEP_JUICE_BLACK ->
+ "background/season-2024-intothedeep/field-2024-juice-black.png"
+ }
+
+ val isDarkMode = path.contains("dark", ignoreCase = true)
+ Pair(path, isDarkMode)
+ }
// Get the path and dark mode boolean from the background map
val (path, isDarkMode) = backgroundMap[background]!!
@@ -471,24 +544,42 @@ class MeepMeep @JvmOverloads constructor(
/**
* Sets the compass image for the MeepMeep application.
*
- * @param compassImage The [CompassImage] enum representing the wanted compass image.
- * @return The [MeepMeep] instance for method chaining.
+ * @param compassImage The [CompassImage] enum representing the wanted
+ *
+ * ```
+ * compass image.
+ * @return
+ * ```
+ *
+ * The [MeepMeep] instance for method chaining.
*/
fun setCompassImage(compassImage: CompassImage): MeepMeep {
val classLoader = Thread.currentThread().contextClassLoader
- val compassImageMap = CompassImage.values().associateWith { img ->
- val path = when (img) {
- CompassImage.SIMPLE -> if (colorManager.isDarkMode) "misc/simple-compass-white.png" else "misc/simple-compass-black.png"
- CompassImage.SIMPLE_BLACK -> "misc/simple-compass-black.png"
- CompassImage.SIMPLE_WHITE -> "misc/simple-compass-white.png"
- CompassImage.COMPASS_ROSE -> if (colorManager.isDarkMode) "misc/compass-rose-white-text.png" else "misc/compass-rose-black-text.png"
- CompassImage.COMPASS_ROSE_WHITE -> "misc/compass-rose-white-text.png"
- CompassImage.COMPASS_ROSE_BLACK -> "misc/compass-rose-black-text.png"
- }
-
- path
- }
+ val compassImageMap =
+ CompassImage.values().associateWith { img ->
+ val path =
+ when (img) {
+ CompassImage.SIMPLE ->
+ if (colorManager.isDarkMode) "misc/simple-compass-white.png"
+ else "misc/simple-compass-black.png"
+
+ CompassImage.SIMPLE_BLACK -> "misc/simple-compass-black.png"
+ CompassImage.SIMPLE_WHITE -> "misc/simple-compass-white.png"
+ CompassImage.COMPASS_ROSE ->
+ if (colorManager.isDarkMode)
+ "misc/compass-rose-white-text.png"
+ else "misc/compass-rose-black-text.png"
+
+ CompassImage.COMPASS_ROSE_WHITE ->
+ "misc/compass-rose-white-text.png"
+
+ CompassImage.COMPASS_ROSE_BLACK ->
+ "misc/compass-rose-black-text.png"
+ }
+
+ path
+ }
// Get the path and dark mode boolean from the compass image map
val path = compassImageMap[compassImage]!!
@@ -501,7 +592,8 @@ class MeepMeep @JvmOverloads constructor(
/**
* Sets the compass image for the MeepMeep application.
*
- * This method scales the provided [Image] and sets it as the compass image, allowing for custom compass images.
+ * This method scales the provided [Image] and sets it as the compass
+ * image, allowing for custom compass images.
*
* @param image The [Image] to be set as the compass image.
* @return The [MeepMeep] instance for method chaining.
@@ -524,9 +616,7 @@ class MeepMeep @JvmOverloads constructor(
* @param x The x-coordinate for displaying the mouse coordinates.
* @param y The y-coordinate for displaying the mouse coordinates.
*/
- fun setMouseCoordinateDisplayPosition(
- x: Int, y: Int
- ) {
+ fun setMouseCoordinateDisplayPosition(x: Int, y: Int) {
// Update the x-coordinate for the mouse coordinate display
mouseCoordinateDisplayX = x
@@ -538,8 +628,13 @@ class MeepMeep @JvmOverloads constructor(
* Sets the visibility of the FPS display.
*
* @param showFPS A boolean indicating whether the FPS display should be
+ *
+ * ```
* shown.
- * @return The [MeepMeep] instance for method chaining.
+ * @return
+ * ```
+ *
+ * The [MeepMeep] instance for method chaining.
*/
fun setShowFPS(showFPS: Boolean): MeepMeep {
// Update the showFPS property
@@ -557,15 +652,19 @@ class MeepMeep @JvmOverloads constructor(
*
* @param schemeLight The [ColorScheme] to be used for the light theme.
* @param schemeDark The [ColorScheme] to be used for the dark theme.
+ *
+ * ```
* Defaults to the light theme if not provided.
- * @return The [MeepMeep] instance for method chaining.
+ * @return
+ * ```
+ *
+ * The [MeepMeep] instance for method chaining.
+ *
* @see [ColorManager]
* @see [refreshTheme]
*/
@JvmOverloads
- fun setTheme(
- schemeLight: ColorScheme, schemeDark: ColorScheme = schemeLight
- ): MeepMeep {
+ fun setTheme(schemeLight: ColorScheme, schemeDark: ColorScheme = schemeLight): MeepMeep {
// Set the light and dark themes in the ColorManager
colorManager.setTheme(schemeLight, schemeDark)
@@ -589,9 +688,7 @@ class MeepMeep @JvmOverloads constructor(
*/
private fun refreshTheme() {
// Core Refresh: Update the theme for all entities that implement ThemedEntity
- entityList.forEach {
- if (it is ThemedEntity) it.switchScheme(colorManager.theme)
- }
+ entityList.forEach { if (it is ThemedEntity) it.switchScheme(colorManager.theme) }
// Update the background color of the main content pane and canvas panel
windowFrame.contentPane.background = colorManager.theme.uiMainBG
@@ -606,8 +703,14 @@ class MeepMeep @JvmOverloads constructor(
* chaining.
*
* @param isDarkMode A boolean indicating whether dark mode should be
+ *
+ * ```
* enabled.
- * @return The [MeepMeep] instance for method chaining.
+ * @return
+ * ```
+ *
+ * The [MeepMeep] instance for method chaining.
+ *
* @see [ColorManager]
*/
fun setDarkMode(isDarkMode: Boolean): MeepMeep {
@@ -618,6 +721,249 @@ class MeepMeep @JvmOverloads constructor(
return this
}
+ /**
+ * Exports the current trajectory as an image for use in posters,
+ * portfolios, etc.
+ *
+ * @param filePath The path where the image should be saved.
+ * @return The [MeepMeep] instance for method chaining.
+ */
+ fun exportTrajectoryImage(filePath: String): MeepMeep {
+ val exportImage = BufferedImage(windowX, windowY, BufferedImage.TYPE_INT_ARGB)
+ val g = exportImage.createGraphics()
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)
+ g.setRenderingHint(
+ RenderingHints.KEY_INTERPOLATION,
+ RenderingHints.VALUE_INTERPOLATION_BICUBIC
+ )
+ g.setRenderingHint(
+ RenderingHints.KEY_COLOR_RENDERING,
+ RenderingHints.VALUE_COLOR_RENDER_QUALITY
+ )
+ g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE)
+
+ // Draw the Field Background
+ bg?.let { background ->
+ val scaledBg = background.getScaledInstance(windowX, windowY, Image.SCALE_SMOOTH)
+ g.drawImage(scaledBg, 0, 0, null)
+ }
+
+ // Define the shift vector
+ val shiftVector = Vector2d(0.0, 0.5) // Adjust this vector as needed
+
+ // Constants for drawing
+ val strokeWidth = 5.0
+ val turnIndicatorRadius = 7.5
+ val arrowHeadLength = 1.5
+ val arrowLinesAngle = 45.0.toRadians()
+ val endTrimDistance = 2.5
+
+ entityList.forEach { entity ->
+ if (entity is RoadRunnerBotEntity) {
+ entity.currentTrajectorySequence?.let { sequence ->
+ // Stroke for all trajectory segments
+ g.stroke = BasicStroke(
+ strokeWidth.toFloat(),
+ BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND
+ )
+
+ // For each segment in the sequence
+ for (i in 0 until sequence.size()) {
+ when (val segment = sequence.get(i)) {
+ is TrajectorySegment -> {
+ // Draw the trajectory path
+ val path = Path2D.Double() // Create a new path
+ val trajectory = segment.trajectory // Get the trajectory
+ val endHeading = trajectory.end().heading // Get the end heading
+ var lastDrawnPoint: Vector2d? = null
+
+ // Sample points along the trajectory
+ val pathLength = trajectory.path.length()
+ val numPoints = (pathLength / 0.1).toInt()
+ var firstPointDrawn = false
+
+ for (j in 0..numPoints) {
+ val t = j.toDouble() / numPoints
+ val currentDistance = pathLength * t
+
+ // Skip points that are within endTrimDistance from either end
+ if (currentDistance < endTrimDistance || currentDistance > pathLength - endTrimDistance) {
+ continue
+ }
+
+ val point = trajectory.path[currentDistance]
+ val screenPoint = (point.vec() + shiftVector).toScreenCoord()
+ lastDrawnPoint = point.vec() + shiftVector
+
+ if (!firstPointDrawn) {
+ path.moveTo(screenPoint.x, screenPoint.y)
+ firstPointDrawn = true
+ } else {
+ path.lineTo(screenPoint.x, screenPoint.y)
+ }
+ }
+
+ // Draw the path
+ g.color = entity.colorScheme.botBodyColor
+ g.draw(path)
+
+ // Calculate & draw arrow endpoints
+ lastDrawnPoint?.let { lastPoint ->
+ val secondToLastPoint = trajectory.path[pathLength - endTrimDistance - 0.1].vec() + shiftVector
+ val directionVector = lastPoint - secondToLastPoint
+ val pathHeading = atan2(directionVector.y, directionVector.x)
+
+ // Calculate arrow endpoints
+ val screenArrowEndVec1 = (lastPoint + Vector2d(
+ arrowHeadLength,
+ 0.0
+ ).rotated(pathHeading - 180.0.toRadians() + arrowLinesAngle)).toScreenCoord()
+ val screenArrowEndVec2 = (lastPoint + Vector2d(
+ arrowHeadLength,
+ 0.0
+ ).rotated(pathHeading - 180.0.toRadians() - arrowLinesAngle)).toScreenCoord()
+
+ // Draw the arrow lines
+ g.drawLine(
+ lastPoint.toScreenCoord().x.toInt(),
+ lastPoint.toScreenCoord().y.toInt(),
+ screenArrowEndVec1.x.toInt(),
+ screenArrowEndVec1.y.toInt()
+ )
+ g.drawLine(
+ lastPoint.toScreenCoord().x.toInt(),
+ lastPoint.toScreenCoord().y.toInt(),
+ screenArrowEndVec2.x.toInt(),
+ screenArrowEndVec2.y.toInt()
+ )
+ }
+ }
+
+ is TurnSegment -> {
+ // Constants matching TurnIndicatorEntity
+ val turnCircleRadius = 1.0
+ val turnArrowLength = 1.5
+ val turnArrowAngleAdjustment = (-12.5).toRadians()
+
+ // Get the start pose and calculate start/end angles
+ val startPose = segment.startPose
+ val startAngle = startPose.heading
+ val endAngle = startPose.heading + segment.totalRotation
+
+ // Calculate the diagonal angle for the arc
+ val diagonalAngle = (startAngle + endAngle) / 2
+
+ // Convert position to screen coordinates
+ val pos = (startPose.vec() + shiftVector).toScreenCoord()
+
+ // Draw the turn circle
+ g.color = entity.colorScheme.trajectoryTurnColor
+ g.fillOval(
+ (pos.x - turnCircleRadius.scaleInToPixel() / 2).toInt(),
+ (pos.y - turnCircleRadius.scaleInToPixel() / 2).toInt(),
+ turnCircleRadius.scaleInToPixel().toInt(),
+ turnCircleRadius.scaleInToPixel().toInt()
+ )
+
+ // Draw the turn arc
+ g.drawArc(
+ (pos.x - turnIndicatorRadius.scaleInToPixel() / 2).toInt(),
+ (pos.y - turnIndicatorRadius.scaleInToPixel() / 2).toInt(),
+ turnIndicatorRadius.scaleInToPixel().toInt(),
+ turnIndicatorRadius.scaleInToPixel().toInt(),
+ min(
+ startAngle.toDegrees().toInt(),
+ diagonalAngle.toDegrees().toInt()
+ ),
+ abs(
+ endAngle.toDegrees().toInt() - diagonalAngle.toDegrees()
+ .toInt()
+ )
+ )
+
+ // Calculate arrow point
+ val arrowPointVec =
+ Vector2d(
+ turnIndicatorRadius / 2,
+ 0.0
+ ).rotated(diagonalAngle)
+ val translatedPoint =
+ ((startPose.vec() + arrowPointVec) + shiftVector).toScreenCoord()
+
+ // Calculate arrow rotations
+ var arrow1Rotated =
+ diagonalAngle - 90.0.toRadians() + arrowLinesAngle + turnArrowAngleAdjustment
+ if (diagonalAngle < startAngle) arrow1Rotated =
+ 360.0.toRadians() - arrow1Rotated
+
+ var arrow2Rotated =
+ diagonalAngle - 90.0.toRadians() - arrowLinesAngle + turnArrowAngleAdjustment
+ if (diagonalAngle < startAngle) arrow2Rotated =
+ 360.0.toRadians() - arrow2Rotated
+
+ // Calculate arrow endpoints
+ val translatedArrowEndVec1 =
+ ((startPose.vec() + arrowPointVec) + Vector2d(
+ turnArrowLength,
+ 0.0
+ ).rotated(arrow1Rotated) + shiftVector).toScreenCoord()
+ val translatedArrowEndVec2 =
+ ((startPose.vec() + arrowPointVec) + Vector2d(
+ turnArrowLength,
+ 0.0
+ ).rotated(arrow2Rotated) + shiftVector).toScreenCoord()
+
+ // Draw the arrow lines
+ g.drawLine(
+ translatedPoint.x.toInt(),
+ translatedPoint.y.toInt(),
+ translatedArrowEndVec1.x.toInt(),
+ translatedArrowEndVec1.y.toInt()
+ )
+ g.drawLine(
+ translatedPoint.x.toInt(),
+ translatedPoint.y.toInt(),
+ translatedArrowEndVec2.x.toInt(),
+ translatedArrowEndVec2.y.toInt()
+ )
+ }
+
+ is WaitSegment -> {
+ // Constants for the wait circle
+ val waitCircleRadius = 1.0
+
+ // Get the wait pose
+ val waitPose = segment.startPose
+
+ // Convert position to screen coordinates
+ val pos = (waitPose.vec() + shiftVector).toScreenCoord()
+
+ // Draw the wait circle
+ g.color = entity.colorScheme.trajectoryMarkerColor
+ g.fillOval(
+ (pos.x - waitCircleRadius.scaleInToPixel() / 2).toInt(),
+ (pos.y - waitCircleRadius.scaleInToPixel() / 2).toInt(),
+ waitCircleRadius.scaleInToPixel().toInt(),
+ waitCircleRadius.scaleInToPixel().toInt()
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ g.dispose()
+
+ val outputFile = File(filePath)
+ ImageIO.write(exportImage, filePath.substringAfterLast('.').uppercase(), outputFile)
+
+ return this
+ }
+
/**
* Adjusts the canvas dimensions to match the window size and updates all
* entities with the new dimensions.
@@ -670,7 +1016,7 @@ class MeepMeep @JvmOverloads constructor(
* mouse events if applicable. It also adds the entity to the
* [TrajectoryProgressSliderMaster] panel if it is a [RoadRunnerBotEntity],
* and triggers the [EntityEventListener.onAddToEntityList]
- * method if the entity implements [EntityEventListener].
+ * method if the entity implements [EntityEventListener] .
*
* @param entity The [Entity] to be added to the application.
* @return The [MeepMeep] instance for method chaining.
@@ -790,8 +1136,14 @@ class MeepMeep @JvmOverloads constructor(
* 0.0 (completely transparent) and 1.0 (completely opaque).
*
* @param alpha The alpha transparency level to set for the background
+ *
+ * ```
* image.
- * @return The [MeepMeep] instance for method chaining.
+ * @return
+ * ```
+ *
+ * The [MeepMeep] instance for method chaining.
+ *
* @see [MeepMeep.setBackground]
*/
fun setBackgroundAlpha(alpha: Float): MeepMeep {
@@ -852,7 +1204,7 @@ class MeepMeep @JvmOverloads constructor(
enum class CompassImage {
/**
* Simple Compass type, automatically selects black or white text based on
- * color scheme ([ColorManager.isDarkMode]).
+ * color scheme ( [ColorManager.isDarkMode]).
*/
SIMPLE,
@@ -864,7 +1216,7 @@ class MeepMeep @JvmOverloads constructor(
/**
* Compass Rose type, automatically selects white or black text based on
- * color scheme ([ColorManager.isDarkMode]).
+ * color scheme ( [ColorManager.isDarkMode]).
*/
COMPASS_ROSE,
diff --git a/src/main/kotlin/org/rowlandhall/meepmeep/core/entity/BotEntity.kt b/src/main/kotlin/org/rowlandhall/meepmeep/core/entity/BotEntity.kt
index 1dd47e1..3141140 100644
--- a/src/main/kotlin/org/rowlandhall/meepmeep/core/entity/BotEntity.kt
+++ b/src/main/kotlin/org/rowlandhall/meepmeep/core/entity/BotEntity.kt
@@ -46,7 +46,6 @@ open class BotEntity(
override val meepMeep: MeepMeep,
private var width: Double,
private var height: Double,
-
var pose: Pose2d,
private var colorScheme: ColorScheme,
private val opacity: Double
diff --git a/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/entity/TrajectorySequenceEntity.kt b/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/entity/TrajectorySequenceEntity.kt
index cacfbc2..71922e6 100644
--- a/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/entity/TrajectorySequenceEntity.kt
+++ b/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/entity/TrajectorySequenceEntity.kt
@@ -1,15 +1,6 @@
package org.rowlandhall.meepmeep.roadrunner.entity
import com.acmerobotics.roadrunner.geometry.Pose2d
-import org.rowlandhall.meepmeep.MeepMeep
-import org.rowlandhall.meepmeep.core.colorscheme.ColorScheme
-import org.rowlandhall.meepmeep.core.entity.ThemedEntity
-import org.rowlandhall.meepmeep.core.toScreenCoord
-import org.rowlandhall.meepmeep.core.util.FieldUtil
-import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.TrajectorySequence
-import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TrajectorySegment
-import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TurnSegment
-import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.WaitSegment
import java.awt.BasicStroke
import java.awt.Color
import java.awt.Graphics2D
@@ -19,12 +10,21 @@ import java.awt.Transparency
import java.awt.geom.Path2D
import java.awt.image.BufferedImage
import kotlin.math.roundToInt
+import org.rowlandhall.meepmeep.MeepMeep
+import org.rowlandhall.meepmeep.core.colorscheme.ColorScheme
+import org.rowlandhall.meepmeep.core.entity.ThemedEntity
+import org.rowlandhall.meepmeep.core.toScreenCoord
+import org.rowlandhall.meepmeep.core.util.FieldUtil
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.TrajectorySequence
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TrajectorySegment
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TurnSegment
+import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.WaitSegment
class TrajectorySequenceEntity(
- override val meepMeep: MeepMeep,
- private val trajectorySequence: TrajectorySequence,
- private var colorScheme: ColorScheme,
-): ThemedEntity {
+ override val meepMeep: MeepMeep,
+ val trajectorySequence: TrajectorySequence,
+ private var colorScheme: ColorScheme,
+) : ThemedEntity {
/** Tag for the trajectory sequence entity. */
override val tag = "TRAJECTORY_SEQUENCE_ENTITY"
@@ -84,15 +84,11 @@ class TrajectorySequenceEntity(
/** Redraws the entire trajectory path. */
private fun redrawPath() {
// Clear previous turn indicator entities
- turnEntityList.forEach {
- meepMeep.requestToRemoveEntity(it)
- }
+ turnEntityList.forEach { meepMeep.requestToRemoveEntity(it) }
turnEntityList.clear()
// Clear previous marker indicator entities
- markerEntityList.forEach {
- meepMeep.requestToRemoveEntity(it)
- }
+ markerEntityList.forEach { meepMeep.requestToRemoveEntity(it) }
markerEntityList.clear()
// Get the default screen device and configuration
@@ -103,9 +99,9 @@ class TrajectorySequenceEntity(
// Create a compatible image for the trajectory sequence
baseBufferedImage =
config.createCompatibleImage(
- canvasWidth.toInt(),
- canvasHeight.toInt(),
- Transparency.TRANSLUCENT,
+ canvasWidth.toInt(),
+ canvasHeight.toInt(),
+ Transparency.TRANSLUCENT,
)
val gfx = baseBufferedImage.createGraphics()
@@ -119,9 +115,9 @@ class TrajectorySequenceEntity(
// Create strokes for the inner path
val innerStroke =
BasicStroke(
- FieldUtil.scaleInchesToPixel(PATH_INNER_STROKE_WIDTH).toFloat(),
- BasicStroke.CAP_BUTT,
- BasicStroke.JOIN_ROUND,
+ FieldUtil.scaleInchesToPixel(PATH_INNER_STROKE_WIDTH).toFloat(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND,
)
var currentEndPose = trajectorySequence.start()
@@ -135,7 +131,8 @@ class TrajectorySequenceEntity(
// Get the trajectory from the segment
val trajectory = step.trajectory
- // Calculate the number of samples based on the trajectory length and sample resolution
+ // Calculate the number of samples based on the trajectory length and sample
+ // resolution
val displacementSamples =
(trajectory.path.length() / SAMPLE_RESOLUTION).roundToInt()
@@ -157,23 +154,21 @@ class TrajectorySequenceEntity(
// Update the current end pose to the end of the trajectory
currentEndPose = step.trajectory.end()
}
-
is TurnSegment -> {
// Create a turn indicator entity for the turn segment
val turnEntity =
TurnIndicatorEntity(
- meepMeep,
- colorScheme,
- currentEndPose.vec(),
- currentEndPose.heading,
- currentEndPose.heading + step.totalRotation,
+ meepMeep,
+ colorScheme,
+ currentEndPose.vec(),
+ currentEndPose.heading,
+ currentEndPose.heading + step.totalRotation,
)
// Add the turn entity to the list and request to add it to MeepMeep
turnEntityList.add(turnEntity)
meepMeep.requestToAddEntity(turnEntity)
}
-
is WaitSegment -> {
// No action needed for WaitSegment
}
@@ -192,18 +187,21 @@ class TrajectorySequenceEntity(
val pose =
when (segment) {
is WaitSegment -> segment.startPose
- is TurnSegment -> segment.startPose.copy(heading = segment.motionProfile[marker.time].x)
+ is TurnSegment ->
+ segment.startPose.copy(
+ heading = segment.motionProfile[marker.time].x
+ )
else -> Pose2d()
}
// Create a new marker entity with the determined pose and add it to the list
val markerEntity =
MarkerIndicatorEntity(
- meepMeep,
- colorScheme,
- pose,
- marker.callback,
- currentTime + marker.time,
+ meepMeep,
+ colorScheme,
+ pose,
+ marker.callback,
+ currentTime + marker.time,
)
markerEntityList.add(markerEntity)
meepMeep.requestToAddEntity(markerEntity)
@@ -217,11 +215,11 @@ class TrajectorySequenceEntity(
// Create a new marker entity with the determined pose and add it to the list
val markerEntity =
MarkerIndicatorEntity(
- meepMeep,
- colorScheme,
- pose,
- marker.callback,
- currentTime + marker.time,
+ meepMeep,
+ colorScheme,
+ pose,
+ marker.callback,
+ currentTime + marker.time,
)
markerEntityList.add(markerEntity)
meepMeep.requestToAddEntity(markerEntity)
@@ -236,10 +234,10 @@ class TrajectorySequenceEntity(
gfx.color = colorScheme.trajectoryPathColor
gfx.color =
Color(
- colorScheme.trajectoryPathColor.red,
- colorScheme.trajectoryPathColor.green,
- colorScheme.trajectoryPathColor.blue,
- (PATH_UNFOCUSED_OPACITY * 255).toInt(),
+ colorScheme.trajectoryPathColor.red,
+ colorScheme.trajectoryPathColor.green,
+ colorScheme.trajectoryPathColor.blue,
+ (PATH_UNFOCUSED_OPACITY * 255).toInt(),
)
gfx.draw(trajectoryDrawnPath)
}
@@ -260,9 +258,9 @@ class TrajectorySequenceEntity(
// Create a compatible image for the current segment
currentSegmentImage =
config.createCompatibleImage(
- canvasWidth.toInt(),
- canvasHeight.toInt(),
- Transparency.TRANSLUCENT,
+ canvasWidth.toInt(),
+ canvasHeight.toInt(),
+ Transparency.TRANSLUCENT,
)
val gfx = currentSegmentImage!!.createGraphics()
@@ -276,15 +274,15 @@ class TrajectorySequenceEntity(
// Create stroke for the outer and inner paths
val outerStroke =
BasicStroke(
- FieldUtil.scaleInchesToPixel(PATH_OUTER_STROKE_WIDTH).toFloat(),
- BasicStroke.CAP_BUTT,
- BasicStroke.JOIN_ROUND,
+ FieldUtil.scaleInchesToPixel(PATH_OUTER_STROKE_WIDTH).toFloat(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND,
)
val innerStroke =
BasicStroke(
- FieldUtil.scaleInchesToPixel(PATH_INNER_STROKE_WIDTH).toFloat(),
- BasicStroke.CAP_BUTT,
- BasicStroke.JOIN_ROUND,
+ FieldUtil.scaleInchesToPixel(PATH_INNER_STROKE_WIDTH).toFloat(),
+ BasicStroke.CAP_BUTT,
+ BasicStroke.JOIN_ROUND,
)
// Get the trajectory from the current segment
@@ -316,10 +314,10 @@ class TrajectorySequenceEntity(
gfx.stroke = outerStroke
gfx.color =
Color(
- colorScheme.trajectoryPathColor.red,
- colorScheme.trajectoryPathColor.green,
- colorScheme.trajectoryPathColor.blue,
- (PATH_OUTER_OPACITY * 255).toInt(),
+ colorScheme.trajectoryPathColor.red,
+ colorScheme.trajectoryPathColor.green,
+ colorScheme.trajectoryPathColor.blue,
+ (PATH_OUTER_OPACITY * 255).toInt(),
)
gfx.draw(trajectoryDrawnPath)
@@ -330,8 +328,7 @@ class TrajectorySequenceEntity(
}
/**
- * Updates the current segment of the trajectory sequence based on the
- * progress.
+ * Updates the current segment of the trajectory sequence based on the progress.
*
* @param deltaTime The time elapsed since the last update.
*/
@@ -345,7 +342,8 @@ class TrajectorySequenceEntity(
val seg = trajectorySequence.get(i)
if (currentTime + seg.duration > trajectoryProgress!!) {
- // If the current time plus the segment duration exceeds the trajectory progress,
+ // If the current time plus the segment duration exceeds the trajectory
+ // progress,
// set the current segment to this segment
if (seg is TrajectorySegment) currentSegment = seg
@@ -374,9 +372,9 @@ class TrajectorySequenceEntity(
* @param canvasHeight The height of the canvas.
*/
override fun render(
- gfx: Graphics2D,
- canvasWidth: Int,
- canvasHeight: Int,
+ gfx: Graphics2D,
+ canvasWidth: Int,
+ canvasHeight: Int,
) {
// Draw the base buffered image
gfx.drawImage(baseBufferedImage, null, 0, 0)
@@ -386,15 +384,14 @@ class TrajectorySequenceEntity(
}
/**
- * Sets the dimensions of the canvas and redraws the path if the dimensions
- * have changed.
+ * Sets the dimensions of the canvas and redraws the path if the dimensions have changed.
*
* @param canvasWidth The new width of the canvas.
* @param canvasHeight The new height of the canvas.
*/
override fun setCanvasDimensions(
- canvasWidth: Double,
- canvasHeight: Double,
+ canvasWidth: Double,
+ canvasHeight: Double,
) {
// Check if the canvas dimensions have changed
if (this.canvasWidth != canvasWidth || this.canvasHeight != canvasHeight) {
diff --git a/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/trajectorysequence/TrajectorySequenceBuilder.java b/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/trajectorysequence/TrajectorySequenceBuilder.java
index 446e01b..aa5b35e 100644
--- a/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/trajectorysequence/TrajectorySequenceBuilder.java
+++ b/src/main/kotlin/org/rowlandhall/meepmeep/roadrunner/trajectorysequence/TrajectorySequenceBuilder.java
@@ -18,6 +18,7 @@
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryAccelerationConstraint;
import com.acmerobotics.roadrunner.trajectory.constraints.TrajectoryVelocityConstraint;
import com.acmerobotics.roadrunner.util.Angle;
+
import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.SequenceSegment;
import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TrajectorySegment;
import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.TurnSegment;
From 02609275b76a33cfe2665a6d6407837c0b3be33d Mon Sep 17 00:00:00 2001
From: "A. Teo Welton" <76081718+DragonDev07@users.noreply.github.com>
Date: Wed, 6 Nov 2024 14:02:46 -0700
Subject: [PATCH 2/2] Start circle
---
.../org/rowlandhall/meepmeep/MeepMeep.kt | 31 ++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt b/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
index 308b814..8f48516 100644
--- a/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
+++ b/src/main/kotlin/org/rowlandhall/meepmeep/MeepMeep.kt
@@ -23,6 +23,7 @@ import org.rowlandhall.meepmeep.roadrunner.trajectorysequence.sequencesegment.Wa
import org.rowlandhall.meepmeep.roadrunner.ui.TrajectoryProgressSliderMaster
import java.awt.AlphaComposite
import java.awt.BasicStroke
+import java.awt.Color
import java.awt.Font
import java.awt.Graphics2D
import java.awt.Image
@@ -759,6 +760,7 @@ constructor(private val windowX: Int, private val windowY: Int, private val fps:
val arrowHeadLength = 1.5
val arrowLinesAngle = 45.0.toRadians()
val endTrimDistance = 2.5
+ val startCircleRadius = 7.0
entityList.forEach { entity ->
if (entity is RoadRunnerBotEntity) {
@@ -810,12 +812,39 @@ constructor(private val windowX: Int, private val windowY: Int, private val fps:
g.color = entity.colorScheme.botBodyColor
g.draw(path)
+ // Draw starting point circle
+ if (i == 0) {
+ val startPoint =
+ (trajectory.start().vec() + shiftVector).toScreenCoord()
+
+ val originalStroke = g.stroke
+ val originalColor = g.color
+ g.stroke = BasicStroke(
+ 3.0f,
+ BasicStroke.CAP_ROUND,
+ BasicStroke.JOIN_ROUND
+ )
+ g.color = Color(34,139,34)
+
+ g.drawOval(
+ (startPoint.x - startCircleRadius).toInt(),
+ (startPoint.y - startCircleRadius).toInt(),
+ (startCircleRadius * 2).toInt(),
+ (startCircleRadius * 2).toInt()
+ )
+
+ g.stroke = originalStroke
+ g.color = originalColor
+ }
+
+
+
// Calculate & draw arrow endpoints
lastDrawnPoint?.let { lastPoint ->
val secondToLastPoint = trajectory.path[pathLength - endTrimDistance - 0.1].vec() + shiftVector
val directionVector = lastPoint - secondToLastPoint
val pathHeading = atan2(directionVector.y, directionVector.x)
-
+
// Calculate arrow endpoints
val screenArrowEndVec1 = (lastPoint + Vector2d(
arrowHeadLength,