blob: ffb4aee1541670fd7d72fba40a6d422b3aedf342 [file] [log] [blame]
Austin Schuh7c75e582020-11-14 16:41:18 -08001import {aos} from 'aos/configuration_generated';
2import {aos} from 'aos/network/connect_generated';
Alex Perry2124ae82020-03-07 14:19:06 -08003import {Connection} from 'aos/network/www/proxy';
Austin Schuh7c75e582020-11-14 16:41:18 -08004import {frc971} from 'frc971/control_loops/drivetrain/drivetrain_status_generated';
5import {frc971} from 'y2020/vision/sift/sift_generated';
Alex Perry5427c9a2020-02-15 17:43:45 -08006
Alex Perry2124ae82020-03-07 14:19:06 -08007import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
8
Alex Perryb49a3fb2020-02-29 15:26:54 -08009// (0,0) is field center, +X is toward red DS
Alex Perry5427c9a2020-02-15 17:43:45 -080010const FIELD_SIDE_Y = FIELD_WIDTH / 2;
Alex Perryb49a3fb2020-02-29 15:26:54 -080011const FIELD_EDGE_X = FIELD_LENGTH / 2;
Alex Perry5427c9a2020-02-15 17:43:45 -080012
13const DS_WIDTH = 69 * IN_TO_M;
14const DS_ANGLE = 20 * Math.PI / 180;
Alex Perryb49a3fb2020-02-29 15:26:54 -080015const DS_END_X = FIELD_EDGE_X - DS_WIDTH * Math.sin(DS_ANGLE);
Alex Perry5427c9a2020-02-15 17:43:45 -080016const DS_INSIDE_Y = FIELD_SIDE_Y - DS_WIDTH * Math.cos(DS_ANGLE);
17
Alex Perryb49a3fb2020-02-29 15:26:54 -080018const TRENCH_X = 108 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080019const TRENCH_WIDTH = 55.5 * IN_TO_M;
20const TRENCH_INSIDE = FIELD_SIDE_Y - TRENCH_WIDTH;
21
22const SPINNER_LENGTH = 30 * IN_TO_M;
Alex Perryb49a3fb2020-02-29 15:26:54 -080023const SPINNER_TOP_X = 374.59 * IN_TO_M - FIELD_EDGE_X;
Alex Perry5427c9a2020-02-15 17:43:45 -080024const SPINNER_BOTTOM_X = SPINNER_TOP_X - SPINNER_LENGTH;
25
Alex Perryb49a3fb2020-02-29 15:26:54 -080026const SHIELD_BOTTOM_X = -116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080027const SHIELD_BOTTOM_Y = 43.75 * IN_TO_M;
28
Alex Perryb49a3fb2020-02-29 15:26:54 -080029const SHIELD_TOP_X = 116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080030const SHIELD_TOP_Y = -43.75 * IN_TO_M;
31
Alex Perryb49a3fb2020-02-29 15:26:54 -080032const SHIELD_RIGHT_X = -51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080033const SHIELD_RIGHT_Y = -112.88 * IN_TO_M;
34
Alex Perryb49a3fb2020-02-29 15:26:54 -080035const SHIELD_LEFT_X = 51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080036const SHIELD_LEFT_Y = 112.88 * IN_TO_M;
37
38const SHIELD_CENTER_TOP_X = (SHIELD_TOP_X + SHIELD_LEFT_X) / 2
39const SHIELD_CENTER_TOP_Y = (SHIELD_TOP_Y + SHIELD_LEFT_Y) / 2
40
41const SHIELD_CENTER_BOTTOM_X = (SHIELD_BOTTOM_X + SHIELD_RIGHT_X) / 2
42const SHIELD_CENTER_BOTTOM_Y = (SHIELD_BOTTOM_Y + SHIELD_RIGHT_Y) / 2
43
Alex Perryb49a3fb2020-02-29 15:26:54 -080044const INITIATION_X = FIELD_EDGE_X - 120 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080045
Alex Perryb49a3fb2020-02-29 15:26:54 -080046const TARGET_ZONE_TIP_X = FIELD_EDGE_X - 30 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080047const TARGET_ZONE_WIDTH = 48 * IN_TO_M;
48const LOADING_ZONE_WIDTH = 60 * IN_TO_M;
49
Alex Perry2124ae82020-03-07 14:19:06 -080050const ROBOT_WIDTH = 28 * IN_TO_M;
51const ROBOT_LENGTH = 30 * IN_TO_M;
52
Alex Perryb49a3fb2020-02-29 15:26:54 -080053/**
54 * All the messages that are required to display camera information on the field.
55 * Messages not readable on the server node are ignored.
56 */
57const REQUIRED_CHANNELS = [
58 {
59 name: '/pi1/camera',
60 type: 'frc971.vision.sift.ImageMatchResult',
61 },
62 {
63 name: '/pi2/camera',
64 type: 'frc971.vision.sift.ImageMatchResult',
65 },
66 {
67 name: '/pi3/camera',
68 type: 'frc971.vision.sift.ImageMatchResult',
69 },
70 {
71 name: '/pi4/camera',
72 type: 'frc971.vision.sift.ImageMatchResult',
73 },
74 {
75 name: '/pi5/camera',
76 type: 'frc971.vision.sift.ImageMatchResult',
77 },
Alex Perry2124ae82020-03-07 14:19:06 -080078 {
79 name: '/drivetrain',
80 type: 'frc971.control_loops.drivetrain.Status',
81 },
Alex Perryb49a3fb2020-02-29 15:26:54 -080082];
83
Alex Perry5427c9a2020-02-15 17:43:45 -080084export class FieldHandler {
85 private canvas = document.createElement('canvas');
Austin Schuh7c75e582020-11-14 16:41:18 -080086 private imageMatchResult: frc971.vision.sift.ImageMatchResult|null = null;
Philipp Schradera227d042020-11-14 17:33:52 -080087 private drivetrainStatus: DrivetrainStatus|null = null;
Alex Perry5427c9a2020-02-15 17:43:45 -080088
Alex Perryb49a3fb2020-02-29 15:26:54 -080089 constructor(private readonly connection: Connection) {
Alex Perry5427c9a2020-02-15 17:43:45 -080090 document.body.appendChild(this.canvas);
Alex Perryb49a3fb2020-02-29 15:26:54 -080091
92 this.connection.addConfigHandler(() => {
93 this.sendConnect();
94 });
Austin Schuh7c75e582020-11-14 16:41:18 -080095 this.connection.addHandler(
96 frc971.vision.sift.ImageMatchResult.getFullyQualifiedName(), (res) => {
97 this.handleImageMatchResult(res);
98 });
99 this.connection.addHandler(frc971.control_loops.drivetrain.Status.getFullyQualifiedName(), (data) => {
Alex Perry2124ae82020-03-07 14:19:06 -0800100 this.handleDrivetrainStatus(data);
101 });
Alex Perryb49a3fb2020-02-29 15:26:54 -0800102 }
103
104 private handleImageMatchResult(data: Uint8Array): void {
105 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuh7c75e582020-11-14 16:41:18 -0800106 this.imageMatchResult =
107 frc971.vision.sift.ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800108 }
109
Alex Perry2124ae82020-03-07 14:19:06 -0800110 private handleDrivetrainStatus(data: Uint8Array): void {
111 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuh7c75e582020-11-14 16:41:18 -0800112 this.drivetrainStatus = frc971.control_loops.drivetrain.Status.getRootAsStatus(fbBuffer);
Alex Perry2124ae82020-03-07 14:19:06 -0800113 }
114
Alex Perryb49a3fb2020-02-29 15:26:54 -0800115 private sendConnect(): void {
116 const builder = new flatbuffers.Builder(512);
117 const channels: flatbuffers.Offset[] = [];
118 for (const channel of REQUIRED_CHANNELS) {
119 const nameFb = builder.createString(channel.name);
120 const typeFb = builder.createString(channel.type);
Austin Schuh7c75e582020-11-14 16:41:18 -0800121 aos.Channel.startChannel(builder);
122 aos.Channel.addName(builder, nameFb);
123 aos.Channel.addType(builder, typeFb);
124 const channelFb = aos.Channel.endChannel(builder);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800125 channels.push(channelFb);
126 }
127
Austin Schuh7c75e582020-11-14 16:41:18 -0800128 const channelsFb = aos.message_bridge.Connect.createChannelsToTransferVector(builder, channels);
129 aos.message_bridge.Connect.startConnect(builder);
130 aos.message_bridge.Connect.addChannelsToTransfer(builder, channelsFb);
131 const connect = aos.message_bridge.Connect.endConnect(builder);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800132 builder.finish(connect);
133 this.connection.sendConnectMessage(builder);
Alex Perry5427c9a2020-02-15 17:43:45 -0800134 }
135
136 drawField(): void {
137 const MY_COLOR = 'red';
138 const OTHER_COLOR = 'blue';
139 const ctx = this.canvas.getContext('2d');
140 // draw perimiter
141 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800142 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800143 ctx.lineTo(DS_END_X, FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800144 ctx.lineTo(-DS_END_X, FIELD_SIDE_Y);
145 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y);
146 ctx.lineTo(-FIELD_EDGE_X, -DS_INSIDE_Y);
147 ctx.lineTo(-DS_END_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800148 ctx.lineTo(DS_END_X, -FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800149 ctx.lineTo(FIELD_EDGE_X, -DS_INSIDE_Y);
150 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800151 ctx.stroke();
152
153 // draw shield generator
154 ctx.beginPath();
155 ctx.moveTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
156 ctx.lineTo(SHIELD_RIGHT_X, SHIELD_RIGHT_Y);
157 ctx.lineTo(SHIELD_TOP_X, SHIELD_TOP_Y);
158 ctx.lineTo(SHIELD_LEFT_X, SHIELD_LEFT_Y);
159 ctx.lineTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
160 ctx.moveTo(SHIELD_CENTER_TOP_X, SHIELD_CENTER_TOP_Y);
161 ctx.lineTo(SHIELD_CENTER_BOTTOM_X, SHIELD_CENTER_BOTTOM_Y);
162 ctx.stroke();
163
Alex Perryb49a3fb2020-02-29 15:26:54 -0800164 this.drawHalfField(ctx, 'red');
165 ctx.rotate(Math.PI);
166 this.drawHalfField(ctx, 'blue');
167 ctx.rotate(Math.PI);
168 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800169
Alex Perryb49a3fb2020-02-29 15:26:54 -0800170 drawHalfField(ctx, color: string): void {
171 // trenches
172 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800173 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800174 ctx.moveTo(TRENCH_X, FIELD_SIDE_Y);
175 ctx.lineTo(TRENCH_X, TRENCH_INSIDE);
176 ctx.lineTo(-TRENCH_X, TRENCH_INSIDE);
177 ctx.lineTo(-TRENCH_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800178 ctx.stroke();
179
180 ctx.strokeStyle = 'black';
181 ctx.beginPath();
182 ctx.moveTo(SPINNER_TOP_X, FIELD_SIDE_Y);
183 ctx.lineTo(SPINNER_TOP_X, TRENCH_INSIDE);
184 ctx.lineTo(SPINNER_BOTTOM_X, TRENCH_INSIDE);
185 ctx.lineTo(SPINNER_BOTTOM_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800186 ctx.stroke();
187
Alex Perry5427c9a2020-02-15 17:43:45 -0800188 ctx.beginPath();
189 ctx.moveTo(INITIATION_X, FIELD_SIDE_Y);
190 ctx.lineTo(INITIATION_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800191 ctx.stroke();
192
Alex Perryb49a3fb2020-02-29 15:26:54 -0800193 // target/loading
194 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800195 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800196 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800197 ctx.lineTo(TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * TARGET_ZONE_WIDTH);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800198 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y - TARGET_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800199
Alex Perryb49a3fb2020-02-29 15:26:54 -0800200 ctx.moveTo(-FIELD_EDGE_X, DS_INSIDE_Y);
201 ctx.lineTo(-TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * LOADING_ZONE_WIDTH);
202 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y - LOADING_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800203 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800204 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800205
Alex Perryb49a3fb2020-02-29 15:26:54 -0800206 drawCamera(x: number, y: number, theta: number): void {
207 const ctx = this.canvas.getContext('2d');
208 ctx.save();
209 ctx.translate(x, y);
210 ctx.rotate(theta);
Alex Perry5427c9a2020-02-15 17:43:45 -0800211 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800212 ctx.moveTo(0.5, 0.5);
213 ctx.lineTo(0, 0);
214 ctx.lineTo(0.5, -0.5);
Alex Perry5427c9a2020-02-15 17:43:45 -0800215 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800216 ctx.beginPath();
217 ctx.arc(0, 0, 0.25, -Math.PI/4, Math.PI/4);
218 ctx.stroke();
219 ctx.restore();
220 }
221
Alex Perry2124ae82020-03-07 14:19:06 -0800222 drawRobot(x: number, y: number, theta: number): void {
223 const ctx = this.canvas.getContext('2d');
224 ctx.save();
225 ctx.translate(x, y);
226 ctx.rotate(theta);
227 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
228 ctx.stroke();
229 ctx.beginPath();
230 ctx.moveTo(0, 0);
231 ctx.lineTo(ROBOT_LENGTH / 2, 0);
232 ctx.stroke();
233 ctx.restore();
234 }
235
Alex Perryb49a3fb2020-02-29 15:26:54 -0800236 draw(): void {
237 this.reset();
238 this.drawField();
239 //draw cameras
240 if (this.imageMatchResult) {
Philipp Schradera227d042020-11-14 17:33:52 -0800241 for (let i = 0; i < this.imageMatchResult.cameraPosesLength(); i++) {
Alex Perryb49a3fb2020-02-29 15:26:54 -0800242 const pose = this.imageMatchResult.cameraPoses(i);
243 const mat = pose.fieldToCamera();
244 const x = mat.data(3);
245 const y = mat.data(7);
Alex Perry2124ae82020-03-07 14:19:06 -0800246 const theta = Math.atan2(
247 -mat.data(8),
248 Math.sqrt(Math.pow(mat.data(9), 2) + Math.pow(mat.data(10), 2)));
249 this.drawCamera(x, y, theta);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800250 }
251 }
252
Alex Perry2124ae82020-03-07 14:19:06 -0800253 if (this.drivetrainStatus) {
254 this.drawRobot(
255 this.drivetrainStatus.x(), this.drivetrainStatus.y(),
256 this.drivetrainStatus.theta());
257 }
258
Alex Perryb49a3fb2020-02-29 15:26:54 -0800259 window.requestAnimationFrame(() => this.draw());
Alex Perry5427c9a2020-02-15 17:43:45 -0800260 }
261
262 reset(): void {
263 const ctx = this.canvas.getContext('2d');
264 ctx.setTransform(1, 0, 0, 1, 0, 0);
265 const size = window.innerHeight * 0.9;
266 ctx.canvas.height = size;
Alex Perryb49a3fb2020-02-29 15:26:54 -0800267 const width = size / 2 + 20;
268 ctx.canvas.width = width;
269 ctx.clearRect(0, 0, size, width);
Alex Perry5427c9a2020-02-15 17:43:45 -0800270
Alex Perryb49a3fb2020-02-29 15:26:54 -0800271 // Translate to center of display.
272 ctx.translate(width / 2, size / 2);
Alex Perry5427c9a2020-02-15 17:43:45 -0800273 // Coordinate system is:
274 // x -> forward.
275 // y -> to the left.
276 ctx.rotate(-Math.PI / 2);
277 ctx.scale(1, -1);
Alex Perry5427c9a2020-02-15 17:43:45 -0800278
279 const M_TO_PX = (size - 10) / FIELD_LENGTH;
280 ctx.scale(M_TO_PX, M_TO_PX);
281 ctx.lineWidth = 1 / M_TO_PX;
282 }
283}