diff --git a/src/App.svelte b/src/App.svelte index 0187924..3ec0c16 100644 --- a/src/App.svelte +++ b/src/App.svelte @@ -90,6 +90,21 @@ })); } + function getRobotDisplayXY(xy: BasePoint, headingDeg: number): BasePoint { + const headingRad = (headingDeg * Math.PI) / 180; + const offsetX = x(settings.rOffsetX) - x(0); + const offsetY = y(settings.rOffsetY) - y(0); + const offsetXRot = + offsetX * Math.cos(headingRad) - offsetY * Math.sin(headingRad); + const offsetYRot = + offsetX * Math.sin(headingRad) + offsetY * Math.cos(headingRad); + + return { + x: xy.x + offsetXRot, + y: xy.y + offsetYRot, + }; + } + // Canvas state let two: Two; let twoElement: HTMLDivElement; @@ -1038,6 +1053,8 @@ settings.rWidth, settings.rHeight, 50, + settings.rOffsetX, + settings.rOffsetY, ); if (ghostPoints.length >= 3) { @@ -1112,6 +1129,8 @@ settings.rWidth, settings.rHeight, 50, + settings.rOffsetX, + settings.rOffsetY, ); if (ghostPoints.length >= 3) { @@ -1184,6 +1203,8 @@ settings.rWidth, settings.rHeight, 50, + settings.rOffsetX, + settings.rOffsetY, ); if (ghostPoints.length >= 3) { @@ -1257,6 +1278,8 @@ settings.rWidth, settings.rHeight, spacing, + settings.rOffsetX, + settings.rOffsetY, ); // If user requested onion layers only for the next point, filter to the relevant line @@ -1369,6 +1392,8 @@ settings.rWidth, settings.rHeight, spacing, + settings.rOffsetX, + settings.rOffsetY, ); // If user requested onion layers only for the next point, filter to the relevant line @@ -1640,6 +1665,11 @@ ctx.globalAlpha = opacity; ctx.translate(xy.x * scale, xy.y * scale); ctx.rotate((headingDeg * Math.PI) / 180); + // Apply robot center offset (rotated with the robot) + ctx.translate( + settings.rOffsetX * scale, + settings.rOffsetY * scale + ); ctx.drawImage( robotImage, (-robotPixelWidth * scale) / 2, @@ -3094,11 +3124,12 @@ {#if $activePaths.length === 0} + {@const mainRobotDisplayXY = getRobotDisplayXY(robotXY, robotHeading)} Robot { @@ -3111,7 +3142,7 @@ pointer-events: none;`} {#if settings.showHeadingArrow} @@ -3144,11 +3175,12 @@ pointer-events: none;`} {/if} {#if $activePaths.length === 0 && $dualPathMode} + {@const secondRobotDisplayXY = getRobotDisplayXY(secondRobotXY, secondRobotHeading)} Robot 2 { @@ -3161,7 +3193,7 @@ pointer-events: none; opacity: 0.8;`} {#if settings.showHeadingArrow} @@ -3195,11 +3227,12 @@ pointer-events: none; opacity: 0.8;`} {#if $activePaths.length > 0} {#each additionalRobotStates as robotState, idx} + {@const displayXY = getRobotDisplayXY(robotState.xy, robotState.heading)} Robot {idx + 1} { @@ -3212,7 +3245,7 @@ pointer-events: none; opacity: ${1.0 - idx * 0.15};`} {#if settings.showHeadingArrow} diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 92d3cf8..8298bd0 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -6,6 +6,8 @@ import { getRandomColor } from "../utils"; */ export const DEFAULT_ROBOT_WIDTH = 16; export const DEFAULT_ROBOT_HEIGHT = 16; +export const DEFAULT_ROBOT_OFFSET_X = 0; +export const DEFAULT_ROBOT_OFFSET_Y = 0; /** * Default canvas drawing settings @@ -34,6 +36,8 @@ export const DEFAULT_SETTINGS: Settings = { kFriction: 0.1, rWidth: DEFAULT_ROBOT_WIDTH, rHeight: DEFAULT_ROBOT_HEIGHT, + rOffsetX: DEFAULT_ROBOT_OFFSET_X, + rOffsetY: DEFAULT_ROBOT_OFFSET_Y, safetyMargin: 1, maxVelocity: 40, maxAcceleration: 30, diff --git a/src/lib/components/SettingsDialog.svelte b/src/lib/components/SettingsDialog.svelte index 12051bd..da1b048 100644 --- a/src/lib/components/SettingsDialog.svelte +++ b/src/lib/components/SettingsDialog.svelte @@ -10,7 +10,7 @@ // Track which sections are collapsed let collapsedSections = { - robot: true, + robot: false, motion: true, advanced: true, theme: true, @@ -258,6 +258,48 @@ /> +
+ + + handleNumberInput(e.target.value, "rOffsetX")} + class="w-full px-3 py-2 rounded-md border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ +
+ + + handleNumberInput(e.target.value, "rOffsetY")} + class="w-full px-3 py-2 rounded-md border border-neutral-300 dark:border-neutral-600 bg-white dark:bg-neutral-800 focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+