Skip to content

Commit f0efee8

Browse files
Nataso08Copilot
andcommitted
Added robot centre offsets configuration
Co-authored-by: Copilot <[email protected]>
1 parent f54357e commit f0efee8

6 files changed

Lines changed: 121 additions & 18 deletions

File tree

src/App.svelte

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,21 @@
9090
}));
9191
}
9292
93+
function getRobotDisplayXY(xy: BasePoint, headingDeg: number): BasePoint {
94+
const headingRad = (headingDeg * Math.PI) / 180;
95+
const offsetX = x(settings.rOffsetX) - x(0);
96+
const offsetY = y(settings.rOffsetY) - y(0);
97+
const offsetXRot =
98+
offsetX * Math.cos(headingRad) - offsetY * Math.sin(headingRad);
99+
const offsetYRot =
100+
offsetX * Math.sin(headingRad) + offsetY * Math.cos(headingRad);
101+
102+
return {
103+
x: xy.x + offsetXRot,
104+
y: xy.y + offsetYRot,
105+
};
106+
}
107+
93108
// Canvas state
94109
let two: Two;
95110
let twoElement: HTMLDivElement;
@@ -1038,6 +1053,8 @@
10381053
settings.rWidth,
10391054
settings.rHeight,
10401055
50,
1056+
settings.rOffsetX,
1057+
settings.rOffsetY,
10411058
);
10421059
10431060
if (ghostPoints.length >= 3) {
@@ -1112,6 +1129,8 @@
11121129
settings.rWidth,
11131130
settings.rHeight,
11141131
50,
1132+
settings.rOffsetX,
1133+
settings.rOffsetY,
11151134
);
11161135
11171136
if (ghostPoints.length >= 3) {
@@ -1184,6 +1203,8 @@
11841203
settings.rWidth,
11851204
settings.rHeight,
11861205
50,
1206+
settings.rOffsetX,
1207+
settings.rOffsetY,
11871208
);
11881209
11891210
if (ghostPoints.length >= 3) {
@@ -1257,6 +1278,8 @@
12571278
settings.rWidth,
12581279
settings.rHeight,
12591280
spacing,
1281+
settings.rOffsetX,
1282+
settings.rOffsetY,
12601283
);
12611284
12621285
// If user requested onion layers only for the next point, filter to the relevant line
@@ -1369,6 +1392,8 @@
13691392
settings.rWidth,
13701393
settings.rHeight,
13711394
spacing,
1395+
settings.rOffsetX,
1396+
settings.rOffsetY,
13721397
);
13731398
13741399
// If user requested onion layers only for the next point, filter to the relevant line
@@ -1640,6 +1665,11 @@
16401665
ctx.globalAlpha = opacity;
16411666
ctx.translate(xy.x * scale, xy.y * scale);
16421667
ctx.rotate((headingDeg * Math.PI) / 180);
1668+
// Apply robot center offset (rotated with the robot)
1669+
ctx.translate(
1670+
settings.rOffsetX * scale,
1671+
settings.rOffsetY * scale
1672+
);
16431673
ctx.drawImage(
16441674
robotImage,
16451675
(-robotPixelWidth * scale) / 2,
@@ -3094,11 +3124,12 @@
30943124
<MathTools {x} {y} {twoElement} {robotXY} />
30953125
<!-- Main robot: only show in normal mode -->
30963126
{#if $activePaths.length === 0}
3127+
{@const mainRobotDisplayXY = getRobotDisplayXY(robotXY, robotHeading)}
30973128
<img
30983129
src={settings.robotImage || "/robot.png"}
30993130
alt="Robot"
3100-
style={`position: absolute; top: ${robotXY.y}px;
3101-
left: ${robotXY.x}px; transform: translate(-50%, -50%) rotate(${robotHeading}deg); z-index: 20; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
3131+
style={`position: absolute; top: ${mainRobotDisplayXY.y}px;
3132+
left: ${mainRobotDisplayXY.x}px; transform: translate(-50%, -50%) rotate(${robotHeading}deg); z-index: 20; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
31023133
pointer-events: none;`}
31033134
draggable="false"
31043135
on:error={(e) => {
@@ -3111,7 +3142,7 @@ pointer-events: none;`}
31113142
<!-- Heading arrow for main robot -->
31123143
{#if settings.showHeadingArrow}
31133144
<svg
3114-
style={`position: absolute; top: ${robotXY.y}px; left: ${robotXY.x}px; z-index: 21; pointer-events: none; overflow: visible;`}
3145+
style={`position: absolute; top: ${mainRobotDisplayXY.y}px; left: ${mainRobotDisplayXY.x}px; z-index: 21; pointer-events: none; overflow: visible;`}
31153146
width="1"
31163147
height="1"
31173148
>
@@ -3144,11 +3175,12 @@ pointer-events: none;`}
31443175
{/if}
31453176
<!-- Second robot: only show in dual path mode (not multi-path mode) -->
31463177
{#if $activePaths.length === 0 && $dualPathMode}
3178+
{@const secondRobotDisplayXY = getRobotDisplayXY(secondRobotXY, secondRobotHeading)}
31473179
<img
31483180
src={settings.robotImage || "/robot.png"}
31493181
alt="Robot 2"
3150-
style={`position: absolute; top: ${secondRobotXY.y}px;
3151-
left: ${secondRobotXY.x}px; transform: translate(-50%, -50%) rotate(${secondRobotHeading}deg); z-index: 19; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
3182+
style={`position: absolute; top: ${secondRobotDisplayXY.y}px;
3183+
left: ${secondRobotDisplayXY.x}px; transform: translate(-50%, -50%) rotate(${secondRobotHeading}deg); z-index: 19; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
31523184
pointer-events: none; opacity: 0.8;`}
31533185
draggable="false"
31543186
on:error={(e) => {
@@ -3161,7 +3193,7 @@ pointer-events: none; opacity: 0.8;`}
31613193
<!-- Heading arrow for second robot -->
31623194
{#if settings.showHeadingArrow}
31633195
<svg
3164-
style={`position: absolute; top: ${secondRobotXY.y}px; left: ${secondRobotXY.x}px; z-index: 19; pointer-events: none; overflow: visible; opacity: 0.8;`}
3196+
style={`position: absolute; top: ${secondRobotDisplayXY.y}px; left: ${secondRobotDisplayXY.x}px; z-index: 19; pointer-events: none; overflow: visible; opacity: 0.8;`}
31653197
width="1"
31663198
height="1"
31673199
>
@@ -3195,11 +3227,12 @@ pointer-events: none; opacity: 0.8;`}
31953227
<!-- Additional robots: only show in multi-path mode -->
31963228
{#if $activePaths.length > 0}
31973229
{#each additionalRobotStates as robotState, idx}
3230+
{@const displayXY = getRobotDisplayXY(robotState.xy, robotState.heading)}
31983231
<img
31993232
src={settings.robotImage || "/robot.png"}
32003233
alt="Robot {idx + 1}"
3201-
style={`position: absolute; top: ${robotState.xy.y}px;
3202-
left: ${robotState.xy.x}px; transform: translate(-50%, -50%) rotate(${robotState.heading}deg); z-index: ${20 - idx}; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
3234+
style={`position: absolute; top: ${displayXY.y}px;
3235+
left: ${displayXY.x}px; transform: translate(-50%, -50%) rotate(${robotState.heading}deg); z-index: ${20 - idx}; width: ${x(robotWidth)}px; height: ${x(robotHeight)}px;user-select: none; -webkit-user-select: none; -moz-user-select: none;-ms-user-select: none;
32033236
pointer-events: none; opacity: ${1.0 - idx * 0.15};`}
32043237
draggable="false"
32053238
on:error={(e) => {
@@ -3212,7 +3245,7 @@ pointer-events: none; opacity: ${1.0 - idx * 0.15};`}
32123245
<!-- Heading arrow for additional robots -->
32133246
{#if settings.showHeadingArrow}
32143247
<svg
3215-
style={`position: absolute; top: ${robotState.xy.y}px; left: ${robotState.xy.x}px; z-index: ${20 - idx}; pointer-events: none; overflow: visible; opacity: ${1.0 - idx * 0.15};`}
3248+
style={`position: absolute; top: ${displayXY.y}px; left: ${displayXY.x}px; z-index: ${20 - idx}; pointer-events: none; overflow: visible; opacity: ${1.0 - idx * 0.15};`}
32163249
width="1"
32173250
height="1"
32183251
>

src/config/defaults.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { getRandomColor } from "../utils";
66
*/
77
export const DEFAULT_ROBOT_WIDTH = 16;
88
export const DEFAULT_ROBOT_HEIGHT = 16;
9+
export const DEFAULT_ROBOT_OFFSET_X = 0;
10+
export const DEFAULT_ROBOT_OFFSET_Y = 0;
911

1012
/**
1113
* Default canvas drawing settings
@@ -34,6 +36,8 @@ export const DEFAULT_SETTINGS: Settings = {
3436
kFriction: 0.1,
3537
rWidth: DEFAULT_ROBOT_WIDTH,
3638
rHeight: DEFAULT_ROBOT_HEIGHT,
39+
rOffsetX: DEFAULT_ROBOT_OFFSET_X,
40+
rOffsetY: DEFAULT_ROBOT_OFFSET_Y,
3741
safetyMargin: 1,
3842
maxVelocity: 40,
3943
maxAcceleration: 30,

src/lib/components/SettingsDialog.svelte

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
1111
// Track which sections are collapsed
1212
let collapsedSections = {
13-
robot: true,
13+
robot: false,
1414
motion: true,
1515
advanced: true,
1616
theme: true,
@@ -258,6 +258,48 @@
258258
/>
259259
</div>
260260

261+
<div>
262+
<label
263+
for="robot-offset-x"
264+
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1"
265+
>
266+
Robot Offset X (in)
267+
<div class="text-xs text-neutral-500 dark:text-neutral-400">
268+
Horizontal offset from the robot center to the wheel center
269+
</div>
270+
</label>
271+
<input
272+
id="robot-offset-x"
273+
type="number"
274+
value={settings.rOffsetX}
275+
step="0.1"
276+
on:input={(e) =>
277+
handleNumberInput(e.target.value, "rOffsetX")}
278+
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"
279+
/>
280+
</div>
281+
282+
<div>
283+
<label
284+
for="robot-offset-y"
285+
class="block text-sm font-medium text-neutral-700 dark:text-neutral-300 mb-1"
286+
>
287+
Robot Offset Y (in)
288+
<div class="text-xs text-neutral-500 dark:text-neutral-400">
289+
Vertical offset from the robot center to the wheel center
290+
</div>
291+
</label>
292+
<input
293+
id="robot-offset-y"
294+
type="number"
295+
value={settings.rOffsetY}
296+
step="0.1"
297+
on:input={(e) =>
298+
handleNumberInput(e.target.value, "rOffsetY")}
299+
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"
300+
/>
301+
</div>
302+
261303
<div>
262304
<label
263305
for="safety-margin"

src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ export interface Settings {
8484
kFriction: number;
8585
rWidth: number;
8686
rHeight: number;
87+
rOffsetX: number;
88+
rOffsetY: number;
8789
safetyMargin: number;
8890
maxVelocity: number; // inches/sec
8991
maxAcceleration: number; // inches/sec²

src/utils/animation.ts

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,8 @@ export function generateGhostPathPoints(
425425
robotWidth: number,
426426
robotHeight: number,
427427
samples: number = 200, // Higher default for smoother turns
428+
offsetX: number = 0,
429+
offsetY: number = 0,
428430
): BasePoint[] {
429431
if (lines.length === 0) return [];
430432

@@ -474,23 +476,30 @@ export function generateGhostPathPoints(
474476
}
475477
}
476478

477-
// Build left/right rails directly from center + normal offsets
478479
const headingRad = (heading * Math.PI) / 180;
480+
const offsetXRot = offsetX * Math.cos(headingRad) - offsetY * Math.sin(headingRad);
481+
const offsetYRot = offsetX * Math.sin(headingRad) + offsetY * Math.cos(headingRad);
482+
const centerPoint = {
483+
x: robotPosInches.x + offsetXRot,
484+
y: robotPosInches.y + offsetYRot,
485+
};
486+
487+
// Build left/right rails directly from center + normal offsets
479488
const nx = -Math.sin(headingRad);
480489
const ny = Math.cos(headingRad);
481490
const halfW = robotWidth / 2;
482491

483492
const leftPoint = {
484-
x: robotPosInches.x + nx * halfW,
485-
y: robotPosInches.y + ny * halfW,
493+
x: centerPoint.x + nx * halfW,
494+
y: centerPoint.y + ny * halfW,
486495
};
487496
const rightPoint = {
488-
x: robotPosInches.x - nx * halfW,
489-
y: robotPosInches.y - ny * halfW,
497+
x: centerPoint.x - nx * halfW,
498+
y: centerPoint.y - ny * halfW,
490499
};
491500

492501
robotStates.push({
493-
center: { x: robotPosInches.x, y: robotPosInches.y },
502+
center: centerPoint,
494503
heading,
495504
left: leftPoint,
496505
right: rightPoint,
@@ -511,6 +520,8 @@ export function generateGhostPathPoints(
511520
heading,
512521
robotWidth,
513522
robotHeight,
523+
offsetX,
524+
offsetY,
514525
);
515526
return corners;
516527
}
@@ -592,6 +603,8 @@ export function generateOnionLayers(
592603
robotWidth: number,
593604
robotHeight: number,
594605
spacing: number = 6,
606+
offsetX: number = 0,
607+
offsetY: number = 0,
595608
): Array<{ x: number; y: number; heading: number; corners: BasePoint[]; lineIndex: number }> {
596609
if (lines.length === 0) return [];
597610

@@ -600,6 +613,7 @@ export function generateOnionLayers(
600613
y: number;
601614
heading: number;
602615
corners: BasePoint[];
616+
lineIndex: number;
603617
}> = [];
604618

605619
// Calculate total path length
@@ -702,6 +716,8 @@ export function generateOnionLayers(
702716
heading,
703717
robotWidth,
704718
robotHeight,
719+
offsetX,
720+
offsetY,
705721
);
706722

707723
layers.push({

src/utils/geometry.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ export function getRobotCorners(
116116
heading: number,
117117
width: number,
118118
height: number,
119+
offsetX: number = 0,
120+
offsetY: number = 0,
119121
): BasePoint[] {
120122
// Convert heading from degrees to radians
121123
const headingRad = (heading * Math.PI) / 180;
@@ -128,6 +130,10 @@ export function getRobotCorners(
128130
const cos = Math.cos(headingRad);
129131
const sin = Math.sin(headingRad);
130132

133+
// Apply center offset (rotated with the robot)
134+
const offsetXRot = offsetX * cos - offsetY * sin;
135+
const offsetYRot = offsetX * sin + offsetY * cos;
136+
131137
// Corner offsets relative to center (before rotation)
132138
// Define corners in local robot frame:
133139
// - width extends perpendicular to heading direction (left/right)
@@ -142,8 +148,8 @@ export function getRobotCorners(
142148
// Rotate and translate corners
143149
// Using standard 2D rotation matrix for screen coordinates
144150
return corners.map((corner) => ({
145-
x: x + corner.dx * cos - corner.dy * sin,
146-
y: y + corner.dx * sin + corner.dy * cos,
151+
x: x + offsetXRot + corner.dx * cos - corner.dy * sin,
152+
y: y + offsetYRot + corner.dx * sin + corner.dy * cos,
147153
}));
148154
}
149155

0 commit comments

Comments
 (0)