Skip to content

Commit 026dded

Browse files
committed
refactor drawing utils
1 parent 82c2bca commit 026dded

File tree

2 files changed

+116
-76
lines changed

2 files changed

+116
-76
lines changed

src/frontend/components/TrackMap/TrackCanvas.tsx

Lines changed: 14 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@ import { getColor, getTailwindStyle } from '@irdashies/utils/colors';
55
import { shouldShowTrack } from './tracks/broken-tracks';
66
import { TrackDebug } from './TrackDebug';
77
import { useStartFinishLine } from './hooks/useStartFinishLine';
8+
import {
9+
setupCanvasContext,
10+
drawTrack,
11+
drawStartFinishLine,
12+
drawTurnNames,
13+
drawDrivers,
14+
} from './trackDrawingUtils';
815

916
export interface TrackProps {
1017
trackId: number;
@@ -209,83 +216,14 @@ export const TrackCanvas = ({
209216
const offsetX = (rect.width - TRACK_DRAWING_WIDTH * scale) / 2;
210217
const offsetY = (rect.height - TRACK_DRAWING_HEIGHT * scale) / 2;
211218

212-
// Save context state
213-
ctx.save();
214-
215-
// Apply scaling and centering
216-
ctx.translate(offsetX, offsetY);
217-
ctx.scale(scale, scale);
218-
219-
// Shadow
220-
ctx.shadowColor = 'black';
221-
ctx.shadowBlur = 2;
222-
ctx.shadowOffsetX = 1;
223-
ctx.shadowOffsetY = 1;
224-
225-
// Draw track
226-
if (path2DObjects.inside) {
227-
// Draw black outline first
228-
ctx.strokeStyle = 'black';
229-
ctx.lineWidth = 40;
230-
ctx.stroke(path2DObjects.inside);
231-
232-
// Draw white track on top
233-
ctx.strokeStyle = 'white';
234-
ctx.lineWidth = 20;
235-
ctx.stroke(path2DObjects.inside);
236-
}
237-
238-
// Draw start/finish line
239-
if (startFinishLine) {
240-
const lineLength = 60; // Length of the start/finish line
241-
const { point: sfPoint, perpendicular } = startFinishLine;
242-
243-
// Calculate the start and end points of the line
244-
const startX = sfPoint.x - (perpendicular.x * lineLength) / 2;
245-
const startY = sfPoint.y - (perpendicular.y * lineLength) / 2;
246-
const endX = sfPoint.x + (perpendicular.x * lineLength) / 2;
247-
const endY = sfPoint.y + (perpendicular.y * lineLength) / 2;
248-
249-
ctx.lineWidth = 20;
250-
ctx.strokeStyle = getColor('red');
251-
ctx.lineCap = 'square';
252-
253-
// Draw the perpendicular line
254-
ctx.beginPath();
255-
ctx.moveTo(startX, startY);
256-
ctx.lineTo(endX, endY);
257-
ctx.stroke();
258-
}
259-
260-
// Draw turn numbers
261-
if (enableTurnNames) {
262-
trackDrawing.turns?.forEach((turn) => {
263-
if (!turn.content || !turn.x || !turn.y) return;
264-
ctx.textAlign = 'center';
265-
ctx.textBaseline = 'middle';
266-
ctx.fillStyle = 'white';
267-
ctx.font = '2rem sans-serif';
268-
ctx.fillText(turn.content, turn.x, turn.y);
269-
});
270-
}
219+
// Setup canvas context with scaling and shadow
220+
setupCanvasContext(ctx, scale, offsetX, offsetY);
271221

272-
// Draw drivers
273-
Object.values(calculatePositions)
274-
.sort((a, b) => Number(a.isPlayer) - Number(b.isPlayer)) // draws player last to be on top
275-
.forEach(({ driver, position }) => {
276-
const color = driverColors[driver.CarIdx];
277-
if (!color) return;
278-
279-
ctx.fillStyle = color.fill;
280-
ctx.beginPath();
281-
ctx.arc(position.x, position.y, 40, 0, 2 * Math.PI);
282-
ctx.fill();
283-
ctx.textAlign = 'center';
284-
ctx.textBaseline = 'middle';
285-
ctx.fillStyle = color.text;
286-
ctx.font = '2rem sans-serif';
287-
ctx.fillText(driver.CarNumber, position.x, position.y);
288-
});
222+
// Draw all elements
223+
drawTrack(ctx, path2DObjects);
224+
drawStartFinishLine(ctx, startFinishLine);
225+
drawTurnNames(ctx, trackDrawing.turns, enableTurnNames);
226+
drawDrivers(ctx, calculatePositions, driverColors);
289227

290228
// Restore context state
291229
ctx.restore();
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { getColor } from '@irdashies/utils/colors';
2+
import { TrackDrawing, TrackDriver } from './TrackCanvas';
3+
4+
export const setupCanvasContext = (
5+
ctx: CanvasRenderingContext2D,
6+
scale: number,
7+
offsetX: number,
8+
offsetY: number
9+
) => {
10+
ctx.save();
11+
ctx.translate(offsetX, offsetY);
12+
ctx.scale(scale, scale);
13+
14+
// Apply shadow
15+
ctx.shadowColor = 'black';
16+
ctx.shadowBlur = 2;
17+
ctx.shadowOffsetX = 1;
18+
ctx.shadowOffsetY = 1;
19+
};
20+
21+
export const drawTrack = (
22+
ctx: CanvasRenderingContext2D,
23+
path2DObjects: { inside: Path2D | null }
24+
) => {
25+
if (!path2DObjects.inside) return;
26+
27+
// Draw black outline first
28+
ctx.strokeStyle = 'black';
29+
ctx.lineWidth = 40;
30+
ctx.stroke(path2DObjects.inside);
31+
32+
// Draw white track on top
33+
ctx.strokeStyle = 'white';
34+
ctx.lineWidth = 20;
35+
ctx.stroke(path2DObjects.inside);
36+
};
37+
38+
export const drawStartFinishLine = (
39+
ctx: CanvasRenderingContext2D,
40+
startFinishLine: { point: { x: number; y: number }; perpendicular: { x: number; y: number } } | null
41+
) => {
42+
if (!startFinishLine) return;
43+
44+
const lineLength = 60; // Length of the start/finish line
45+
const { point: sfPoint, perpendicular } = startFinishLine;
46+
47+
// Calculate the start and end points of the line
48+
const startX = sfPoint.x - (perpendicular.x * lineLength) / 2;
49+
const startY = sfPoint.y - (perpendicular.y * lineLength) / 2;
50+
const endX = sfPoint.x + (perpendicular.x * lineLength) / 2;
51+
const endY = sfPoint.y + (perpendicular.y * lineLength) / 2;
52+
53+
ctx.lineWidth = 20;
54+
ctx.strokeStyle = getColor('red');
55+
ctx.lineCap = 'square';
56+
57+
// Draw the perpendicular line
58+
ctx.beginPath();
59+
ctx.moveTo(startX, startY);
60+
ctx.lineTo(endX, endY);
61+
ctx.stroke();
62+
};
63+
64+
export const drawTurnNames = (
65+
ctx: CanvasRenderingContext2D,
66+
turns: TrackDrawing['turns'],
67+
enableTurnNames: boolean | undefined
68+
) => {
69+
if (!enableTurnNames || !turns) return;
70+
71+
turns.forEach((turn) => {
72+
if (!turn.content || !turn.x || !turn.y) return;
73+
ctx.textAlign = 'center';
74+
ctx.textBaseline = 'middle';
75+
ctx.fillStyle = 'white';
76+
ctx.font = '2rem sans-serif';
77+
ctx.fillText(turn.content, turn.x, turn.y);
78+
});
79+
};
80+
81+
export const drawDrivers = (
82+
ctx: CanvasRenderingContext2D,
83+
calculatePositions: Record<number, TrackDriver & { position: { x: number; y: number } }>,
84+
driverColors: Record<number, { fill: string; text: string }>
85+
) => {
86+
Object.values(calculatePositions)
87+
.sort((a, b) => Number(a.isPlayer) - Number(b.isPlayer)) // draws player last to be on top
88+
.forEach(({ driver, position }) => {
89+
const color = driverColors[driver.CarIdx];
90+
if (!color) return;
91+
92+
ctx.fillStyle = color.fill;
93+
ctx.beginPath();
94+
ctx.arc(position.x, position.y, 40, 0, 2 * Math.PI);
95+
ctx.fill();
96+
ctx.textAlign = 'center';
97+
ctx.textBaseline = 'middle';
98+
ctx.fillStyle = color.text;
99+
ctx.font = '2rem sans-serif';
100+
ctx.fillText(driver.CarNumber, position.x, position.y);
101+
});
102+
};

0 commit comments

Comments
 (0)