blob: c0b6c5091edec6cc0832f77811cf69f84a091eac [file] [log] [blame]
Philipp Schradere625ba22020-11-16 20:11:37 -08001import * as configuration from 'org_frc971/aos/configuration_generated';
2import * as connect from 'org_frc971/aos/network/connect_generated';
3import {Connection} from 'org_frc971/aos/network/www/proxy';
4import * as flatbuffers_builder from 'org_frc971/external/com_github_google_flatbuffers/ts/builder';
5import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
6import * as drivetrain from 'org_frc971/frc971/control_loops/drivetrain/drivetrain_status_generated';
7import * as sift from 'org_frc971/y2020/vision/sift/sift_generated';
8
9import DrivetrainStatus = drivetrain.frc971.control_loops.drivetrain.Status;
10import ImageMatchResult = sift.frc971.vision.sift.ImageMatchResult;
11import Connect = connect.aos.message_bridge.Connect;
12import Channel = configuration.aos.Channel;
Alex Perry5427c9a2020-02-15 17:43:45 -080013
Alex Perry2124ae82020-03-07 14:19:06 -080014import {FIELD_LENGTH, FIELD_WIDTH, FT_TO_M, IN_TO_M} from './constants';
15
Alex Perryb49a3fb2020-02-29 15:26:54 -080016// (0,0) is field center, +X is toward red DS
Alex Perry5427c9a2020-02-15 17:43:45 -080017const FIELD_SIDE_Y = FIELD_WIDTH / 2;
Alex Perryb49a3fb2020-02-29 15:26:54 -080018const FIELD_EDGE_X = FIELD_LENGTH / 2;
Alex Perry5427c9a2020-02-15 17:43:45 -080019
20const DS_WIDTH = 69 * IN_TO_M;
21const DS_ANGLE = 20 * Math.PI / 180;
Alex Perryb49a3fb2020-02-29 15:26:54 -080022const DS_END_X = FIELD_EDGE_X - DS_WIDTH * Math.sin(DS_ANGLE);
Alex Perry5427c9a2020-02-15 17:43:45 -080023const DS_INSIDE_Y = FIELD_SIDE_Y - DS_WIDTH * Math.cos(DS_ANGLE);
24
Alex Perryb49a3fb2020-02-29 15:26:54 -080025const TRENCH_X = 108 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080026const TRENCH_WIDTH = 55.5 * IN_TO_M;
27const TRENCH_INSIDE = FIELD_SIDE_Y - TRENCH_WIDTH;
28
29const SPINNER_LENGTH = 30 * IN_TO_M;
Alex Perryb49a3fb2020-02-29 15:26:54 -080030const SPINNER_TOP_X = 374.59 * IN_TO_M - FIELD_EDGE_X;
Alex Perry5427c9a2020-02-15 17:43:45 -080031const SPINNER_BOTTOM_X = SPINNER_TOP_X - SPINNER_LENGTH;
32
Alex Perryb49a3fb2020-02-29 15:26:54 -080033const SHIELD_BOTTOM_X = -116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080034const SHIELD_BOTTOM_Y = 43.75 * IN_TO_M;
35
Alex Perryb49a3fb2020-02-29 15:26:54 -080036const SHIELD_TOP_X = 116 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080037const SHIELD_TOP_Y = -43.75 * IN_TO_M;
38
Alex Perryb49a3fb2020-02-29 15:26:54 -080039const SHIELD_RIGHT_X = -51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080040const SHIELD_RIGHT_Y = -112.88 * IN_TO_M;
41
Alex Perryb49a3fb2020-02-29 15:26:54 -080042const SHIELD_LEFT_X = 51.06 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080043const SHIELD_LEFT_Y = 112.88 * IN_TO_M;
44
45const SHIELD_CENTER_TOP_X = (SHIELD_TOP_X + SHIELD_LEFT_X) / 2
46const SHIELD_CENTER_TOP_Y = (SHIELD_TOP_Y + SHIELD_LEFT_Y) / 2
47
48const SHIELD_CENTER_BOTTOM_X = (SHIELD_BOTTOM_X + SHIELD_RIGHT_X) / 2
49const SHIELD_CENTER_BOTTOM_Y = (SHIELD_BOTTOM_Y + SHIELD_RIGHT_Y) / 2
50
Alex Perryb49a3fb2020-02-29 15:26:54 -080051const INITIATION_X = FIELD_EDGE_X - 120 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080052
Alex Perryb49a3fb2020-02-29 15:26:54 -080053const TARGET_ZONE_TIP_X = FIELD_EDGE_X - 30 * IN_TO_M;
Alex Perry5427c9a2020-02-15 17:43:45 -080054const TARGET_ZONE_WIDTH = 48 * IN_TO_M;
55const LOADING_ZONE_WIDTH = 60 * IN_TO_M;
56
Alex Perry2124ae82020-03-07 14:19:06 -080057const ROBOT_WIDTH = 28 * IN_TO_M;
58const ROBOT_LENGTH = 30 * IN_TO_M;
59
Alex Perryb49a3fb2020-02-29 15:26:54 -080060/**
61 * All the messages that are required to display camera information on the field.
62 * Messages not readable on the server node are ignored.
63 */
64const REQUIRED_CHANNELS = [
65 {
66 name: '/pi1/camera',
67 type: 'frc971.vision.sift.ImageMatchResult',
68 },
69 {
70 name: '/pi2/camera',
71 type: 'frc971.vision.sift.ImageMatchResult',
72 },
73 {
74 name: '/pi3/camera',
75 type: 'frc971.vision.sift.ImageMatchResult',
76 },
77 {
78 name: '/pi4/camera',
79 type: 'frc971.vision.sift.ImageMatchResult',
80 },
81 {
82 name: '/pi5/camera',
83 type: 'frc971.vision.sift.ImageMatchResult',
84 },
Alex Perry2124ae82020-03-07 14:19:06 -080085 {
86 name: '/drivetrain',
87 type: 'frc971.control_loops.drivetrain.Status',
88 },
Alex Perryb49a3fb2020-02-29 15:26:54 -080089];
90
Alex Perry5427c9a2020-02-15 17:43:45 -080091export class FieldHandler {
92 private canvas = document.createElement('canvas');
Philipp Schradere625ba22020-11-16 20:11:37 -080093 private imageMatchResult: ImageMatchResult|null = null;
Philipp Schradera227d042020-11-14 17:33:52 -080094 private drivetrainStatus: DrivetrainStatus|null = null;
Alex Perry5427c9a2020-02-15 17:43:45 -080095
Alex Perryb49a3fb2020-02-29 15:26:54 -080096 constructor(private readonly connection: Connection) {
Alex Perry5427c9a2020-02-15 17:43:45 -080097 document.body.appendChild(this.canvas);
Alex Perryb49a3fb2020-02-29 15:26:54 -080098
99 this.connection.addConfigHandler(() => {
100 this.sendConnect();
101 });
Austin Schuh7c75e582020-11-14 16:41:18 -0800102 this.connection.addHandler(
Philipp Schradere625ba22020-11-16 20:11:37 -0800103 ImageMatchResult.getFullyQualifiedName(), (res) => {
Austin Schuh7c75e582020-11-14 16:41:18 -0800104 this.handleImageMatchResult(res);
105 });
Philipp Schradere625ba22020-11-16 20:11:37 -0800106 this.connection.addHandler(
107 DrivetrainStatus.getFullyQualifiedName(), (data) => {
108 this.handleDrivetrainStatus(data);
109 });
Alex Perryb49a3fb2020-02-29 15:26:54 -0800110 }
111
112 private handleImageMatchResult(data: Uint8Array): void {
Philipp Schradere625ba22020-11-16 20:11:37 -0800113 const fbBuffer = new ByteBuffer(data);
114 this.imageMatchResult = ImageMatchResult.getRootAsImageMatchResult(
115 fbBuffer as unknown as flatbuffers.ByteBuffer);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800116 }
117
Alex Perry2124ae82020-03-07 14:19:06 -0800118 private handleDrivetrainStatus(data: Uint8Array): void {
Philipp Schradere625ba22020-11-16 20:11:37 -0800119 const fbBuffer = new ByteBuffer(data);
120 this.drivetrainStatus = DrivetrainStatus.getRootAsStatus(
121 fbBuffer as unknown as flatbuffers.ByteBuffer);
Alex Perry2124ae82020-03-07 14:19:06 -0800122 }
123
Alex Perryb49a3fb2020-02-29 15:26:54 -0800124 private sendConnect(): void {
Philipp Schradere625ba22020-11-16 20:11:37 -0800125 const builder =
126 new flatbuffers_builder.Builder(512) as unknown as flatbuffers.Builder;
Alex Perryb49a3fb2020-02-29 15:26:54 -0800127 const channels: flatbuffers.Offset[] = [];
128 for (const channel of REQUIRED_CHANNELS) {
129 const nameFb = builder.createString(channel.name);
130 const typeFb = builder.createString(channel.type);
Philipp Schradere625ba22020-11-16 20:11:37 -0800131 Channel.startChannel(builder);
132 Channel.addName(builder, nameFb);
133 Channel.addType(builder, typeFb);
134 const channelFb = Channel.endChannel(builder);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800135 channels.push(channelFb);
136 }
137
Philipp Schradere625ba22020-11-16 20:11:37 -0800138 const channelsFb =
139 Connect.createChannelsToTransferVector(builder, channels);
140 Connect.startConnect(builder);
141 Connect.addChannelsToTransfer(builder, channelsFb);
142 const connect = Connect.endConnect(builder);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800143 builder.finish(connect);
144 this.connection.sendConnectMessage(builder);
Alex Perry5427c9a2020-02-15 17:43:45 -0800145 }
146
147 drawField(): void {
148 const MY_COLOR = 'red';
149 const OTHER_COLOR = 'blue';
150 const ctx = this.canvas.getContext('2d');
151 // draw perimiter
152 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800153 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800154 ctx.lineTo(DS_END_X, FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800155 ctx.lineTo(-DS_END_X, FIELD_SIDE_Y);
156 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y);
157 ctx.lineTo(-FIELD_EDGE_X, -DS_INSIDE_Y);
158 ctx.lineTo(-DS_END_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800159 ctx.lineTo(DS_END_X, -FIELD_SIDE_Y);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800160 ctx.lineTo(FIELD_EDGE_X, -DS_INSIDE_Y);
161 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800162 ctx.stroke();
163
164 // draw shield generator
165 ctx.beginPath();
166 ctx.moveTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
167 ctx.lineTo(SHIELD_RIGHT_X, SHIELD_RIGHT_Y);
168 ctx.lineTo(SHIELD_TOP_X, SHIELD_TOP_Y);
169 ctx.lineTo(SHIELD_LEFT_X, SHIELD_LEFT_Y);
170 ctx.lineTo(SHIELD_BOTTOM_X, SHIELD_BOTTOM_Y);
171 ctx.moveTo(SHIELD_CENTER_TOP_X, SHIELD_CENTER_TOP_Y);
172 ctx.lineTo(SHIELD_CENTER_BOTTOM_X, SHIELD_CENTER_BOTTOM_Y);
173 ctx.stroke();
174
Alex Perryb49a3fb2020-02-29 15:26:54 -0800175 this.drawHalfField(ctx, 'red');
176 ctx.rotate(Math.PI);
177 this.drawHalfField(ctx, 'blue');
178 ctx.rotate(Math.PI);
179 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800180
Alex Perryb49a3fb2020-02-29 15:26:54 -0800181 drawHalfField(ctx, color: string): void {
182 // trenches
183 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800184 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800185 ctx.moveTo(TRENCH_X, FIELD_SIDE_Y);
186 ctx.lineTo(TRENCH_X, TRENCH_INSIDE);
187 ctx.lineTo(-TRENCH_X, TRENCH_INSIDE);
188 ctx.lineTo(-TRENCH_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800189 ctx.stroke();
190
191 ctx.strokeStyle = 'black';
192 ctx.beginPath();
193 ctx.moveTo(SPINNER_TOP_X, FIELD_SIDE_Y);
194 ctx.lineTo(SPINNER_TOP_X, TRENCH_INSIDE);
195 ctx.lineTo(SPINNER_BOTTOM_X, TRENCH_INSIDE);
196 ctx.lineTo(SPINNER_BOTTOM_X, FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800197 ctx.stroke();
198
Alex Perry5427c9a2020-02-15 17:43:45 -0800199 ctx.beginPath();
200 ctx.moveTo(INITIATION_X, FIELD_SIDE_Y);
201 ctx.lineTo(INITIATION_X, -FIELD_SIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800202 ctx.stroke();
203
Alex Perryb49a3fb2020-02-29 15:26:54 -0800204 // target/loading
205 ctx.strokeStyle = color;
Alex Perry5427c9a2020-02-15 17:43:45 -0800206 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800207 ctx.moveTo(FIELD_EDGE_X, DS_INSIDE_Y);
Alex Perry5427c9a2020-02-15 17:43:45 -0800208 ctx.lineTo(TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * TARGET_ZONE_WIDTH);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800209 ctx.lineTo(FIELD_EDGE_X, DS_INSIDE_Y - TARGET_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800210
Alex Perryb49a3fb2020-02-29 15:26:54 -0800211 ctx.moveTo(-FIELD_EDGE_X, DS_INSIDE_Y);
212 ctx.lineTo(-TARGET_ZONE_TIP_X, DS_INSIDE_Y - 0.5 * LOADING_ZONE_WIDTH);
213 ctx.lineTo(-FIELD_EDGE_X, DS_INSIDE_Y - LOADING_ZONE_WIDTH);
Alex Perry5427c9a2020-02-15 17:43:45 -0800214 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800215 }
Alex Perry5427c9a2020-02-15 17:43:45 -0800216
Alex Perryb49a3fb2020-02-29 15:26:54 -0800217 drawCamera(x: number, y: number, theta: number): void {
218 const ctx = this.canvas.getContext('2d');
219 ctx.save();
220 ctx.translate(x, y);
221 ctx.rotate(theta);
Alex Perry5427c9a2020-02-15 17:43:45 -0800222 ctx.beginPath();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800223 ctx.moveTo(0.5, 0.5);
224 ctx.lineTo(0, 0);
225 ctx.lineTo(0.5, -0.5);
Alex Perry5427c9a2020-02-15 17:43:45 -0800226 ctx.stroke();
Alex Perryb49a3fb2020-02-29 15:26:54 -0800227 ctx.beginPath();
Philipp Schradere625ba22020-11-16 20:11:37 -0800228 ctx.arc(0, 0, 0.25, -Math.PI / 4, Math.PI / 4);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800229 ctx.stroke();
230 ctx.restore();
231 }
232
Alex Perry2124ae82020-03-07 14:19:06 -0800233 drawRobot(x: number, y: number, theta: number): void {
234 const ctx = this.canvas.getContext('2d');
235 ctx.save();
236 ctx.translate(x, y);
237 ctx.rotate(theta);
238 ctx.rect(-ROBOT_LENGTH / 2, -ROBOT_WIDTH / 2, ROBOT_LENGTH, ROBOT_WIDTH);
239 ctx.stroke();
240 ctx.beginPath();
241 ctx.moveTo(0, 0);
242 ctx.lineTo(ROBOT_LENGTH / 2, 0);
243 ctx.stroke();
244 ctx.restore();
245 }
246
Philipp Schradere625ba22020-11-16 20:11:37 -0800247 draw(): void {
Alex Perryb49a3fb2020-02-29 15:26:54 -0800248 this.reset();
249 this.drawField();
Philipp Schradere625ba22020-11-16 20:11:37 -0800250 // draw cameras
Alex Perryb49a3fb2020-02-29 15:26:54 -0800251 if (this.imageMatchResult) {
Philipp Schradera227d042020-11-14 17:33:52 -0800252 for (let i = 0; i < this.imageMatchResult.cameraPosesLength(); i++) {
Alex Perryb49a3fb2020-02-29 15:26:54 -0800253 const pose = this.imageMatchResult.cameraPoses(i);
254 const mat = pose.fieldToCamera();
255 const x = mat.data(3);
256 const y = mat.data(7);
Alex Perry2124ae82020-03-07 14:19:06 -0800257 const theta = Math.atan2(
258 -mat.data(8),
259 Math.sqrt(Math.pow(mat.data(9), 2) + Math.pow(mat.data(10), 2)));
260 this.drawCamera(x, y, theta);
Alex Perryb49a3fb2020-02-29 15:26:54 -0800261 }
262 }
263
Alex Perry2124ae82020-03-07 14:19:06 -0800264 if (this.drivetrainStatus) {
265 this.drawRobot(
266 this.drivetrainStatus.x(), this.drivetrainStatus.y(),
267 this.drivetrainStatus.theta());
268 }
269
Alex Perryb49a3fb2020-02-29 15:26:54 -0800270 window.requestAnimationFrame(() => this.draw());
Alex Perry5427c9a2020-02-15 17:43:45 -0800271 }
272
273 reset(): void {
274 const ctx = this.canvas.getContext('2d');
275 ctx.setTransform(1, 0, 0, 1, 0, 0);
276 const size = window.innerHeight * 0.9;
277 ctx.canvas.height = size;
Alex Perryb49a3fb2020-02-29 15:26:54 -0800278 const width = size / 2 + 20;
279 ctx.canvas.width = width;
280 ctx.clearRect(0, 0, size, width);
Alex Perry5427c9a2020-02-15 17:43:45 -0800281
Alex Perryb49a3fb2020-02-29 15:26:54 -0800282 // Translate to center of display.
283 ctx.translate(width / 2, size / 2);
Alex Perry5427c9a2020-02-15 17:43:45 -0800284 // Coordinate system is:
285 // x -> forward.
286 // y -> to the left.
287 ctx.rotate(-Math.PI / 2);
288 ctx.scale(1, -1);
Alex Perry5427c9a2020-02-15 17:43:45 -0800289
290 const M_TO_PX = (size - 10) / FIELD_LENGTH;
291 ctx.scale(M_TO_PX, M_TO_PX);
292 ctx.lineWidth = 1 / M_TO_PX;
293 }
294}