blob: b8656a2f112d1a1e79ac97f90be3c7d02ecaa4b1 [file] [log] [blame]
Maxwell Hendersonad312342023-01-10 12:07:47 -08001import {ByteBuffer} from 'flatbuffers';
James Kuszmaulf7b5d622023-03-11 15:14:53 -08002
3import {ClientStatistics} from '../../aos/network/message_bridge_client_generated'
4import {ServerStatistics, State as ConnectionState} from '../../aos/network/message_bridge_server_generated'
Maxwell Hendersonad312342023-01-10 12:07:47 -08005import {Connection} from '../../aos/network/www/proxy';
Milo Lin72fb9012023-03-10 19:53:19 -08006import {ZeroingError} from '../../frc971/control_loops/control_loops_generated';
James Kuszmaulf7b5d622023-03-11 15:14:53 -08007import {Status as DrivetrainStatus} from '../../frc971/control_loops/drivetrain/drivetrain_status_generated';
8import {LocalizerOutput} from '../../frc971/control_loops/drivetrain/localization/localizer_output_generated';
9import {ArmState, ArmStatus, EndEffectorState, Status as SuperstructureStatus} from '../control_loops/superstructure/superstructure_status_generated'
10import {RejectionReason} from '../localizer/status_generated';
11import {TargetEstimateDebug, Visualization} from '../localizer/visualization_generated';
12import {Class} from '../vision/game_pieces_generated'
Maxwell Hendersonad312342023-01-10 12:07:47 -080013
14import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
15
16// (0,0) is field center, +X is toward red DS
17const FIELD_SIDE_Y = FIELD_WIDTH / 2;
18const FIELD_EDGE_X = FIELD_LENGTH / 2;
19
James Kuszmaulfb894572023-02-23 17:25:06 -080020const ROBOT_WIDTH = 25 * IN_TO_M;
21const ROBOT_LENGTH = 32 * IN_TO_M;
Maxwell Hendersonad312342023-01-10 12:07:47 -080022
23const PI_COLORS = ['#ff00ff', '#ffff00', '#00ffff', '#ffa500'];
James Kuszmaulfb894572023-02-23 17:25:06 -080024const PIS = ['pi1', 'pi2', 'pi3', 'pi4'];
Maxwell Hendersonad312342023-01-10 12:07:47 -080025
26export class FieldHandler {
27 private canvas = document.createElement('canvas');
James Kuszmaulfb894572023-02-23 17:25:06 -080028 private localizerOutput: LocalizerOutput|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080029 private drivetrainStatus: DrivetrainStatus|null = null;
Milo Line6571c02023-03-04 21:08:20 -080030 private superstructureStatus: SuperstructureStatus|null = null;
Maxwell Hendersonad312342023-01-10 12:07:47 -080031
32 // Image information indexed by timestamp (seconds since the epoch), so that
33 // we can stop displaying images after a certain amount of time.
James Kuszmaulfb894572023-02-23 17:25:06 -080034 private localizerImageMatches = new Map<number, Visualization>();
35 private x: HTMLElement = (document.getElementById('x') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080036 private y: HTMLElement = (document.getElementById('y') as HTMLElement);
37 private theta: HTMLElement =
38 (document.getElementById('theta') as HTMLElement);
Maxwell Hendersonad312342023-01-10 12:07:47 -080039 private imagesAcceptedCounter: HTMLElement =
40 (document.getElementById('images_accepted') as HTMLElement);
James Kuszmaul4f0e4362023-03-12 20:03:59 -070041 private rejectionReasonCells: HTMLElement[][] = [];
James Kuszmaulf7b5d622023-03-11 15:14:53 -080042 private messageBridgeDiv: HTMLElement =
43 (document.getElementById('message_bridge_status') as HTMLElement);
44 private clientStatuses = new Map<string, HTMLElement>();
45 private serverStatuses = new Map<string, HTMLElement>();
Maxwell Hendersonad312342023-01-10 12:07:47 -080046 private fieldImage: HTMLImageElement = new Image();
Milo Line6571c02023-03-04 21:08:20 -080047 private endEffectorState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080048 (document.getElementById('end_effector_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080049 private wrist: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080050 (document.getElementById('wrist') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080051 private armState: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080052 (document.getElementById('arm_state') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080053 private gamePiece: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080054 (document.getElementById('game_piece') as HTMLElement);
55 private armX: HTMLElement = (document.getElementById('arm_x') as HTMLElement);
56 private armY: HTMLElement = (document.getElementById('arm_y') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080057 private circularIndex: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080058 (document.getElementById('arm_circular_index') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080059 private roll: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080060 (document.getElementById('arm_roll') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080061 private proximal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080062 (document.getElementById('arm_proximal') as HTMLElement);
Milo Line6571c02023-03-04 21:08:20 -080063 private distal: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080064 (document.getElementById('arm_distal') as HTMLElement);
Milo Lin72fb9012023-03-10 19:53:19 -080065 private zeroingFaults: HTMLElement =
James Kuszmaulf7b5d622023-03-11 15:14:53 -080066 (document.getElementById('zeroing_faults') as HTMLElement);
67 _
Maxwell Hendersonad312342023-01-10 12:07:47 -080068
69 constructor(private readonly connection: Connection) {
70 (document.getElementById('field') as HTMLElement).appendChild(this.canvas);
71
James Kuszmaulf7b5d622023-03-11 15:14:53 -080072 this.fieldImage.src = '2023.png';
James Kuszmaulfb894572023-02-23 17:25:06 -080073
James Kuszmaul4f0e4362023-03-12 20:03:59 -070074 // Construct a table header.
75 {
76 const row = document.createElement('div');
77 const nameCell = document.createElement('div');
78 nameCell.innerHTML = "Rejection Reason";
79 row.appendChild(nameCell);
80 for (const pi of PIS) {
81 const nodeCell = document.createElement('div');
82 nodeCell.innerHTML = pi;
83 row.appendChild(nodeCell);
84 }
85 document.getElementById('vision_readouts').appendChild(row);
86 }
James Kuszmaulfb894572023-02-23 17:25:06 -080087 for (const value in RejectionReason) {
88 // Typescript generates an iterator that produces both numbers and
89 // strings... don't do anything on the string iterations.
90 if (isNaN(Number(value))) {
91 continue;
92 }
93 const row = document.createElement('div');
94 const nameCell = document.createElement('div');
95 nameCell.innerHTML = RejectionReason[value];
96 row.appendChild(nameCell);
James Kuszmaul4f0e4362023-03-12 20:03:59 -070097 this.rejectionReasonCells.push([]);
98 for (const pi of PIS) {
99 const valueCell = document.createElement('div');
100 valueCell.innerHTML = 'NA';
101 this.rejectionReasonCells[this.rejectionReasonCells.length - 1].push(valueCell);
102 row.appendChild(valueCell);
103 }
James Kuszmaulfb894572023-02-23 17:25:06 -0800104 document.getElementById('vision_readouts').appendChild(row);
105 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800106
107 for (let ii = 0; ii < PI_COLORS.length; ++ii) {
108 const legendEntry = document.createElement('div');
109 legendEntry.style.color = PI_COLORS[ii];
110 legendEntry.innerHTML = 'PI' + (ii + 1).toString()
111 document.getElementById('legend').appendChild(legendEntry);
112 }
113
114 this.connection.addConfigHandler(() => {
115 // Visualization message is reliable so that we can see *all* the vision
116 // matches.
James Kuszmaulfb894572023-02-23 17:25:06 -0800117 for (const pi in PIS) {
118 this.connection.addReliableHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800119 '/' + PIS[pi] + '/camera', 'y2023.localizer.Visualization',
James Kuszmaulfb894572023-02-23 17:25:06 -0800120 (data) => {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700121 this.handleLocalizerDebug(Number(pi), data);
James Kuszmaulfb894572023-02-23 17:25:06 -0800122 });
123 }
Maxwell Hendersonad312342023-01-10 12:07:47 -0800124 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800125 '/drivetrain', 'frc971.control_loops.drivetrain.Status', (data) => {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800126 this.handleDrivetrainStatus(data);
127 });
128 this.connection.addHandler(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800129 '/localizer', 'frc971.controls.LocalizerOutput', (data) => {
James Kuszmaulfb894572023-02-23 17:25:06 -0800130 this.handleLocalizerOutput(data);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800131 });
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800132 this.connection.addHandler(
133 '/superstructure', 'y2023.control_loops.superstructure.Status',
134 (data) => {this.handleSuperstructureStatus(data)});
135 this.connection.addHandler(
136 '/aos', 'aos.message_bridge.ServerStatistics',
137 (data) => {this.handleServerStatistics(data)});
138 this.connection.addHandler(
139 '/aos', 'aos.message_bridge.ClientStatistics',
140 (data) => {this.handleClientStatistics(data)});
Maxwell Hendersonad312342023-01-10 12:07:47 -0800141 });
142 }
143
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700144 private handleLocalizerDebug(pi: number, data: Uint8Array): void {
James Kuszmaulfb894572023-02-23 17:25:06 -0800145 const now = Date.now() / 1000.0;
146
147 const fbBuffer = new ByteBuffer(data);
148 this.localizerImageMatches.set(
149 now, Visualization.getRootAsVisualization(fbBuffer));
150
151 const debug = this.localizerImageMatches.get(now);
152
153 if (debug.statistics()) {
154 if (debug.statistics().rejectionReasonsLength() ==
155 this.rejectionReasonCells.length) {
156 for (let ii = 0; ii < debug.statistics().rejectionReasonsLength();
157 ++ii) {
James Kuszmaul4f0e4362023-03-12 20:03:59 -0700158 this.rejectionReasonCells[ii][pi].innerHTML =
James Kuszmaulfb894572023-02-23 17:25:06 -0800159 debug.statistics().rejectionReasons(ii).count().toString();
160 }
161 } else {
162 console.error('Unexpected number of rejection reasons in counter.');
163 }
164 }
165 }
166
167 private handleLocalizerOutput(data: Uint8Array): void {
168 const fbBuffer = new ByteBuffer(data);
169 this.localizerOutput = LocalizerOutput.getRootAsLocalizerOutput(fbBuffer);
170 }
171
Maxwell Hendersonad312342023-01-10 12:07:47 -0800172 private handleDrivetrainStatus(data: Uint8Array): void {
173 const fbBuffer = new ByteBuffer(data);
174 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
175 }
176
Milo Line6571c02023-03-04 21:08:20 -0800177 private handleSuperstructureStatus(data: Uint8Array): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800178 const fbBuffer = new ByteBuffer(data);
179 this.superstructureStatus = SuperstructureStatus.getRootAsStatus(fbBuffer);
180 }
181
182 private populateNodeConnections(nodeName: string): void {
183 const row = document.createElement('div');
184 this.messageBridgeDiv.appendChild(row);
185 const nodeDiv = document.createElement('div');
186 nodeDiv.innerHTML = nodeName;
187 row.appendChild(nodeDiv);
188 const clientDiv = document.createElement('div');
189 clientDiv.innerHTML = 'N/A';
190 row.appendChild(clientDiv);
191 const serverDiv = document.createElement('div');
192 serverDiv.innerHTML = 'N/A';
193 row.appendChild(serverDiv);
194 this.serverStatuses.set(nodeName, serverDiv);
195 this.clientStatuses.set(nodeName, clientDiv);
196 }
197
198 private setCurrentNodeState(element: HTMLElement, state: ConnectionState):
199 void {
200 if (state === ConnectionState.CONNECTED) {
201 element.innerHTML = ConnectionState[state];
202 element.classList.remove('faulted');
203 element.classList.add('connected');
204 } else {
205 element.innerHTML = ConnectionState[state];
206 element.classList.remove('connected');
207 element.classList.add('faulted');
208 }
209 }
210
211 private handleServerStatistics(data: Uint8Array): void {
212 const fbBuffer = new ByteBuffer(data);
213 const serverStatistics =
214 ServerStatistics.getRootAsServerStatistics(fbBuffer);
215
216 for (let ii = 0; ii < serverStatistics.connectionsLength(); ++ii) {
217 const connection = serverStatistics.connections(ii);
218 const nodeName = connection.node().name();
219 if (!this.serverStatuses.has(nodeName)) {
220 this.populateNodeConnections(nodeName);
221 }
222 this.setCurrentNodeState(
223 this.serverStatuses.get(nodeName), connection.state());
224 }
225 }
226
227 private handleClientStatistics(data: Uint8Array): void {
228 const fbBuffer = new ByteBuffer(data);
229 const clientStatistics =
230 ClientStatistics.getRootAsClientStatistics(fbBuffer);
231
232 for (let ii = 0; ii < clientStatistics.connectionsLength(); ++ii) {
233 const connection = clientStatistics.connections(ii);
234 const nodeName = connection.node().name();
235 if (!this.clientStatuses.has(nodeName)) {
236 this.populateNodeConnections(nodeName);
237 }
238 this.setCurrentNodeState(
239 this.clientStatuses.get(nodeName), connection.state());
240 }
Milo Line6571c02023-03-04 21:08:20 -0800241 }
242
Maxwell Hendersonad312342023-01-10 12:07:47 -0800243 drawField(): void {
244 const ctx = this.canvas.getContext('2d');
245 ctx.save();
James Kuszmaulfb894572023-02-23 17:25:06 -0800246 ctx.scale(1.0, -1.0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800247 ctx.drawImage(
248 this.fieldImage, 0, 0, this.fieldImage.width, this.fieldImage.height,
249 -FIELD_EDGE_X, -FIELD_SIDE_Y, FIELD_LENGTH, FIELD_WIDTH);
250 ctx.restore();
251 }
252
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800253 drawCamera(x: number, y: number, theta: number, color: string = 'blue'):
254 void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800255 const ctx = this.canvas.getContext('2d');
256 ctx.save();
257 ctx.translate(x, y);
258 ctx.rotate(theta);
259 ctx.strokeStyle = color;
260 ctx.beginPath();
261 ctx.moveTo(0.5, 0.5);
262 ctx.lineTo(0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800263 ctx.lineTo(0.5, -0.5);
264 ctx.stroke();
265 ctx.beginPath();
266 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
267 ctx.stroke();
268 ctx.restore();
269 }
270
271 drawRobot(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800272 x: number, y: number, theta: number, color: string = 'blue',
273 dashed: boolean = false): void {
Maxwell Hendersonad312342023-01-10 12:07:47 -0800274 const ctx = this.canvas.getContext('2d');
275 ctx.save();
276 ctx.translate(x, y);
277 ctx.rotate(theta);
278 ctx.strokeStyle = color;
279 ctx.lineWidth = ROBOT_WIDTH / 10.0;
280 if (dashed) {
281 ctx.setLineDash([0.05, 0.05]);
282 } else {
283 // Empty array = solid line.
284 ctx.setLineDash([]);
285 }
286 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
287 ctx.stroke();
288
289 // Draw line indicating which direction is forwards on the robot.
290 ctx.beginPath();
291 ctx.moveTo(0, 0);
James Kuszmaulfb894572023-02-23 17:25:06 -0800292 ctx.lineTo(ROBOT_LENGTH / 2.0, 0);
Maxwell Hendersonad312342023-01-10 12:07:47 -0800293 ctx.stroke();
294
295 ctx.restore();
296 }
297
298 setZeroing(div: HTMLElement): void {
299 div.innerHTML = 'zeroing';
300 div.classList.remove('faulted');
301 div.classList.add('zeroing');
302 div.classList.remove('near');
303 }
304
Milo Line6571c02023-03-04 21:08:20 -0800305 setEstopped(div: HTMLElement): void {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800306 div.innerHTML = 'estopped';
307 div.classList.add('faulted');
308 div.classList.remove('zeroing');
309 div.classList.remove('near');
Milo Line6571c02023-03-04 21:08:20 -0800310 }
311
312 setTargetValue(
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800313 div: HTMLElement, target: number, val: number, tolerance: number): void {
314 div.innerHTML = val.toFixed(4);
315 div.classList.remove('faulted');
316 div.classList.remove('zeroing');
317 if (Math.abs(target - val) < tolerance) {
318 div.classList.add('near');
319 } else {
320 div.classList.remove('near');
321 }
Milo Line6571c02023-03-04 21:08:20 -0800322 }
323
Maxwell Hendersonad312342023-01-10 12:07:47 -0800324 setValue(div: HTMLElement, val: number): void {
325 div.innerHTML = val.toFixed(4);
326 div.classList.remove('faulted');
327 div.classList.remove('zeroing');
328 div.classList.remove('near');
329 }
330
331 draw(): void {
332 this.reset();
333 this.drawField();
334
335 // Draw the matches with debugging information from the localizer.
336 const now = Date.now() / 1000.0;
James Kuszmaulfb894572023-02-23 17:25:06 -0800337
Milo Line6571c02023-03-04 21:08:20 -0800338 if (this.superstructureStatus) {
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800339 this.endEffectorState.innerHTML =
340 EndEffectorState[this.superstructureStatus.endEffectorState()];
341 if (!this.superstructureStatus.wrist() ||
342 !this.superstructureStatus.wrist().zeroed()) {
343 this.setZeroing(this.wrist);
344 } else if (this.superstructureStatus.wrist().estopped()) {
345 this.setEstopped(this.wrist);
346 } else {
347 this.setTargetValue(
348 this.wrist,
349 this.superstructureStatus.wrist().unprofiledGoalPosition(),
350 this.superstructureStatus.wrist().estimatorState().position(),
351 1e-3);
352 }
353 this.armState.innerHTML =
354 ArmState[this.superstructureStatus.arm().state()];
355 this.gamePiece.innerHTML = Class[this.superstructureStatus.gamePiece()];
356 this.armX.innerHTML = this.superstructureStatus.arm().armX().toFixed(2);
357 this.armY.innerHTML = this.superstructureStatus.arm().armY().toFixed(2);
358 this.circularIndex.innerHTML =
359 this.superstructureStatus.arm().armCircularIndex().toFixed(0);
360 this.roll.innerHTML = this.superstructureStatus.arm()
361 .rollJointEstimatorState()
362 .position()
363 .toFixed(2);
364 this.proximal.innerHTML = this.superstructureStatus.arm()
365 .proximalEstimatorState()
366 .position()
367 .toFixed(2);
368 this.distal.innerHTML = this.superstructureStatus.arm()
369 .distalEstimatorState()
370 .position()
371 .toFixed(2);
372 let zeroingErrors: string = 'Roll Joint Errors:' +
373 '<br/>';
374 for (let i = 0; i < this.superstructureStatus.arm()
375 .rollJointEstimatorState()
376 .errors.length;
377 i++) {
378 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
379 .rollJointEstimatorState()
380 .errors(i)] +
381 '<br/>';
382 }
383 zeroingErrors += '<br/>' +
384 'Proximal Joint Errors:' +
385 '<br/>';
386 for (let i = 0; i < this.superstructureStatus.arm()
387 .proximalEstimatorState()
388 .errors.length;
389 i++) {
390 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
391 .proximalEstimatorState()
392 .errors(i)] +
393 '<br/>';
394 }
395 zeroingErrors += '<br/>' +
396 'Distal Joint Errors:' +
397 '<br/>';
398 for (let i = 0; i <
399 this.superstructureStatus.arm().distalEstimatorState().errors.length;
400 i++) {
401 zeroingErrors += ZeroingError[this.superstructureStatus.arm()
402 .distalEstimatorState()
403 .errors(i)] +
404 '<br/>';
405 }
406 zeroingErrors += '<br/>' +
407 'Wrist Errors:' +
408 '<br/>';
409 for (let i = 0;
410 i < this.superstructureStatus.wrist().estimatorState().errors.length;
411 i++) {
412 zeroingErrors += ZeroingError[this.superstructureStatus.wrist()
413 .estimatorState()
414 .errors(i)] +
415 '<br/>';
416 }
417 this.zeroingFaults.innerHTML = zeroingErrors;
Milo Line6571c02023-03-04 21:08:20 -0800418 }
419
Maxwell Hendersonad312342023-01-10 12:07:47 -0800420 if (this.drivetrainStatus && this.drivetrainStatus.trajectoryLogging()) {
421 this.drawRobot(
422 this.drivetrainStatus.trajectoryLogging().x(),
423 this.drivetrainStatus.trajectoryLogging().y(),
James Kuszmaulf7b5d622023-03-11 15:14:53 -0800424 this.drivetrainStatus.trajectoryLogging().theta(), '#000000FF',
Maxwell Hendersonad312342023-01-10 12:07:47 -0800425 false);
426 }
427
James Kuszmaulfb894572023-02-23 17:25:06 -0800428 if (this.localizerOutput) {
429 if (!this.localizerOutput.zeroed()) {
430 this.setZeroing(this.x);
431 this.setZeroing(this.y);
432 this.setZeroing(this.theta);
433 } else {
434 this.setValue(this.x, this.localizerOutput.x());
435 this.setValue(this.y, this.localizerOutput.y());
436 this.setValue(this.theta, this.localizerOutput.theta());
437 }
438
439 this.drawRobot(
440 this.localizerOutput.x(), this.localizerOutput.y(),
441 this.localizerOutput.theta());
442
443 this.imagesAcceptedCounter.innerHTML =
444 this.localizerOutput.imageAcceptedCount().toString();
445 }
446
447 for (const [time, value] of this.localizerImageMatches) {
448 const age = now - time;
449 const kRemovalAge = 1.0;
450 if (age > kRemovalAge) {
451 this.localizerImageMatches.delete(time);
452 continue;
453 }
454 const kMaxImageAlpha = 0.5;
455 const ageAlpha = kMaxImageAlpha * (kRemovalAge - age) / kRemovalAge
456 for (let i = 0; i < value.targetsLength(); i++) {
457 const imageDebug = value.targets(i);
458 const x = imageDebug.impliedRobotX();
459 const y = imageDebug.impliedRobotY();
460 const theta = imageDebug.impliedRobotTheta();
461 const cameraX = imageDebug.cameraX();
462 const cameraY = imageDebug.cameraY();
463 const cameraTheta = imageDebug.cameraTheta();
464 const accepted = imageDebug.accepted();
465 // Make camera readings fade over time.
466 const alpha = Math.round(255 * ageAlpha).toString(16).padStart(2, '0');
467 const dashed = false;
James Kuszmaulfb894572023-02-23 17:25:06 -0800468 const cameraRgb = PI_COLORS[imageDebug.camera()];
469 const cameraRgba = cameraRgb + alpha;
James Kuszmaul122a22b2023-02-25 18:14:15 -0800470 this.drawRobot(x, y, theta, cameraRgba, dashed);
James Kuszmaulfb894572023-02-23 17:25:06 -0800471 this.drawCamera(cameraX, cameraY, cameraTheta, cameraRgba);
472 }
473 }
474
Maxwell Hendersonad312342023-01-10 12:07:47 -0800475 window.requestAnimationFrame(() => this.draw());
476 }
477
478 reset(): void {
479 const ctx = this.canvas.getContext('2d');
480 ctx.setTransform(1, 0, 0, 1, 0, 0);
481 const size = window.innerHeight * 0.9;
482 ctx.canvas.height = size;
483 const width = size / 2 + 20;
484 ctx.canvas.width = width;
485 ctx.clearRect(0, 0, size, width);
486
487 // Translate to center of display.
488 ctx.translate(width / 2, size / 2);
489 // Coordinate system is:
490 // x -> forward.
491 // y -> to the left.
492 ctx.rotate(-Math.PI / 2);
493 ctx.scale(1, -1);
494
495 const M_TO_PX = (size - 10) / FIELD_LENGTH;
496 ctx.scale(M_TO_PX, M_TO_PX);
497 ctx.lineWidth = 1 / M_TO_PX;
498 }
499}