blob: 6ec1afbd2fd00bcec9ca6719f092dd960217d608 [file] [log] [blame]
Alex Perry2124ae82020-03-07 14:19:06 -08001import {Channel, Configuration} from 'aos/configuration_generated';
Alex Perryb49a3fb2020-02-29 15:26:54 -08002import {Connect} from 'aos/network/connect_generated';
Alex Perry2124ae82020-03-07 14:19:06 -08003import {Connection} from 'aos/network/www/proxy';
4import {Status as DrivetrainStatus} from 'frc971/control_loops/drivetrain/drivetrain_status_generated';
Alex Perryb49a3fb2020-02-29 15:26:54 -08005import {ImageMatchResult} 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');
Alex Perry2124ae82020-03-07 14:19:06 -080086 private imageMatchResult: ImageMatchResult|null = null;
87 private drivetrainStatus: DrivetrianStatus|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 });
95 this.connection.addHandler(ImageMatchResult.getFullyQualifiedName(), (res) => {
96 this.handleImageMatchResult(res);
97 });
Alex Perry2124ae82020-03-07 14:19:06 -080098 this.connection.addHandler(DrivetrainStatus.getFullyQualifiedName(), (data) => {
99 this.handleDrivetrainStatus(data);
100 });
Alex Perryb49a3fb2020-02-29 15:26:54 -0800101 }
102
103 private handleImageMatchResult(data: Uint8Array): void {
104 const fbBuffer = new flatbuffers.ByteBuffer(data);
105 this.imageMatchResult = ImageMatchResult.getRootAsImageMatchResult(fbBuffer);
106 }
107
Alex Perry2124ae82020-03-07 14:19:06 -0800108 private handleDrivetrainStatus(data: Uint8Array): void {
109 const fbBuffer = new flatbuffers.ByteBuffer(data);
110 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(fbBuffer);
111 }
112
Alex Perryb49a3fb2020-02-29 15:26:54 -0800113 private sendConnect(): void {
114 const builder = new flatbuffers.Builder(512);
115 const channels: flatbuffers.Offset[] = [];
116 for (const channel of REQUIRED_CHANNELS) {
117 const nameFb = builder.createString(channel.name);
118 const typeFb = builder.createString(channel.type);
119 Channel.startChannel(builder);
120 Channel.addName(builder, nameFb);
121 Channel.addType(builder, typeFb);
122 const channelFb = Channel.endChannel(builder);
123 channels.push(channelFb);
124 }
125
126 const channelsFb = Connect.createChannelsToTransferVector(builder, channels);
127 Connect.startConnect(builder);
128 Connect.addChannelsToTransfer(builder, channelsFb);
129 const connect = Connect.endConnect(builder);
130 builder.finish(connect);
131 this.connection.sendConnectMessage(builder);
Alex Perry5427c9a2020-02-15 17:43:45 -0800132 }
133
134 drawField(): void {
135 const MY_COLOR = 'red';
136 const OTHER_COLOR = 'blue';
137 const ctx = this.canvas.getContext('2d');
138 // draw perimiter
139 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800140 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800141 ctx.lineTo(DS_END_X, FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800142 ctx.lineTo(-DS_END_X, FIELD_SIDE_Y);
143 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y);
144 ctx.lineTo(-FIELD_EDGE_X, -DS_INSIDE_Y);
145 ctx.lineTo(-DS_END_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800146 ctx.lineTo(DS_END_X, -FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800147 ctx.lineTo(FIELD_EDGE_X, -DS_INSIDE_Y);
148 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800149 ctx.stroke();
150
151 // draw shield generator
152 ctx.beginPath();
153 ctx.moveTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
154 ctx.lineTo(SHIELD_RIGHT_X, SHIELD_RIGHT_Y);
155 ctx.lineTo(SHIELD_TOP_X, SHIELD_TOP_Y);
156 ctx.lineTo(SHIELD_LEFT_X, SHIELD_LEFT_Y);
157 ctx.lineTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
158 ctx.moveTo(SHIELD_CENTER_TOP_X, SHIELD_CENTER_TOP_Y);
159 ctx.lineTo(SHIELD_CENTER_BOTTOM_X, SHIELD_CENTER_BOTTOM_Y);
160 ctx.stroke();
161
Alex Perryb49a3fb2020-02-29 15:26:54 -0800162 this.drawHalfField(ctx, 'red');
163 ctx.rotate(Math.PI);
164 this.drawHalfField(ctx, 'blue');
165 ctx.rotate(Math.PI);
166 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800167
Alex Perryb49a3fb2020-02-29 15:26:54 -0800168 drawHalfField(ctx, color: string): void {
169 // trenches
170 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800171 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800172 ctx.moveTo(TRENCH_X, FIELD_SIDE_Y);
173 ctx.lineTo(TRENCH_X, TRENCH_INSIDE);
174 ctx.lineTo(-TRENCH_X, TRENCH_INSIDE);
175 ctx.lineTo(-TRENCH_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800176 ctx.stroke();
177
178 ctx.strokeStyle = 'black';
179 ctx.beginPath();
180 ctx.moveTo(SPINNER_TOP_X, FIELD_SIDE_Y);
181 ctx.lineTo(SPINNER_TOP_X, TRENCH_INSIDE);
182 ctx.lineTo(SPINNER_BOTTOM_X, TRENCH_INSIDE);
183 ctx.lineTo(SPINNER_BOTTOM_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800184 ctx.stroke();
185
Alex Perry5427c9a2020-02-15 17:43:45 -0800186 ctx.beginPath();
187 ctx.moveTo(INITIATION_X, FIELD_SIDE_Y);
188 ctx.lineTo(INITIATION_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800189 ctx.stroke();
190
Alex Perryb49a3fb2020-02-29 15:26:54 -0800191 // target/loading
192 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800193 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800194 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800195 ctx.lineTo(TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * TARGET_ZONE_WIDTH);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800196 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y - TARGET_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800197
Alex Perryb49a3fb2020-02-29 15:26:54 -0800198 ctx.moveTo(-FIELD_EDGE_X, DS_INSIDE_Y);
199 ctx.lineTo(-TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * LOADING_ZONE_WIDTH);
200 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y - LOADING_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800201 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800202 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800203
Alex Perryb49a3fb2020-02-29 15:26:54 -0800204 drawCamera(x: number, y: number, theta: number): void {
205 const ctx = this.canvas.getContext('2d');
206 ctx.save();
207 ctx.translate(x, y);
208 ctx.rotate(theta);
Alex Perry5427c9a2020-02-15 17:43:45 -0800209 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800210 ctx.moveTo(0.5, 0.5);
211 ctx.lineTo(0, 0);
212 ctx.lineTo(0.5, -0.5);
Alex Perry5427c9a2020-02-15 17:43:45 -0800213 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800214 ctx.beginPath();
215 ctx.arc(0, 0, 0.25, -Math.PI/4, Math.PI/4);
216 ctx.stroke();
217 ctx.restore();
218 }
219
Alex Perry2124ae82020-03-07 14:19:06 -0800220 drawRobot(x: number, y: number, theta: number): void {
221 const ctx = this.canvas.getContext('2d');
222 ctx.save();
223 ctx.translate(x, y);
224 ctx.rotate(theta);
225 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
226 ctx.stroke();
227 ctx.beginPath();
228 ctx.moveTo(0, 0);
229 ctx.lineTo(ROBOT_LENGTH / 2, 0);
230 ctx.stroke();
231 ctx.restore();
232 }
233
Alex Perryb49a3fb2020-02-29 15:26:54 -0800234 draw(): void {
235 this.reset();
236 this.drawField();
237 //draw cameras
238 if (this.imageMatchResult) {
Alex Perryb49a3fb2020-02-29 15:26:54 -0800239 for (const i = 0; i < this.imageMatchResult.cameraPosesLength(); i++) {
240 const pose = this.imageMatchResult.cameraPoses(i);
241 const mat = pose.fieldToCamera();
242 const x = mat.data(3);
243 const y = mat.data(7);
Alex Perry2124ae82020-03-07 14:19:06 -0800244 const theta = Math.atan2(
245 -mat.data(8),
246 Math.sqrt(Math.pow(mat.data(9), 2) + Math.pow(mat.data(10), 2)));
247 this.drawCamera(x, y, theta);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800248 }
249 }
250
Alex Perry2124ae82020-03-07 14:19:06 -0800251 if (this.drivetrainStatus) {
252 this.drawRobot(
253 this.drivetrainStatus.x(), this.drivetrainStatus.y(),
254 this.drivetrainStatus.theta());
255 }
256
Alex Perryb49a3fb2020-02-29 15:26:54 -0800257 window.requestAnimationFrame(() => this.draw());
Alex Perry5427c9a2020-02-15 17:43:45 -0800258 }
259
260 reset(): void {
261 const ctx = this.canvas.getContext('2d');
262 ctx.setTransform(1, 0, 0, 1, 0, 0);
263 const size = window.innerHeight * 0.9;
264 ctx.canvas.height = size;
Alex Perryb49a3fb2020-02-29 15:26:54 -0800265 const width = size / 2 + 20;
266 ctx.canvas.width = width;
267 ctx.clearRect(0, 0, size, width);
Alex Perry5427c9a2020-02-15 17:43:45 -0800268
Alex Perryb49a3fb2020-02-29 15:26:54 -0800269 // Translate to center of display.
270 ctx.translate(width / 2, size / 2);
Alex Perry5427c9a2020-02-15 17:43:45 -0800271 // Coordinate system is:
272 // x -> forward.
273 // y -> to the left.
274 ctx.rotate(-Math.PI / 2);
275 ctx.scale(1, -1);
Alex Perry5427c9a2020-02-15 17:43:45 -0800276
277 const M_TO_PX = (size - 10) / FIELD_LENGTH;
278 ctx.scale(M_TO_PX, M_TO_PX);
279 ctx.lineWidth = 1 / M_TO_PX;
280 }
281}