blob: 148245e3f180fed301c970f5c8987ad308c82eab [file] [log] [blame]
James Kuszmaul6271cf72019-09-18 20:10:08 -07001#include "frc971/analysis/plotting/webgl2_animator.h"
2
3namespace frc971 {
4namespace plotting {
5
6namespace {
7struct Button {
8 bool IsTransition(const EmscriptenMouseEvent &mouse_event) {
9 return mouse_event.button == transition_number_;
10 }
11 bool IsPressed(const EmscriptenMouseEvent &mouse_event) {
12 return mouse_event.buttons & (1 << pressed_index_);
13 }
14 const size_t transition_number_;
15 const size_t pressed_index_;
16};
17constexpr Button kLeftButton() { return {0, 0}; }
18//constexpr Button kMiddleButton() { return {1, 2}; }
19constexpr Button kRightButton() { return {2, 1}; }
20
21constexpr Button kPanButton() { return kLeftButton(); }
22constexpr Button kZoomButton() { return kRightButton(); }
23} // namespace
24
25Animator::Animator(const char *canvas_target) : plotter_(canvas_target) {
26 // TODO(james): Write a proper CHECK macro or figure out how to import glog.
27 // Importing glog is a bit of a pain, since it seems to assume all sorts of
28 // things that don't really apply on the web.
29 assert(EMSCRIPTEN_RESULT_SUCCESS ==
30 emscripten_get_canvas_element_size(canvas_target, &canvas_width_,
31 &canvas_height_));
32 assert(EMSCRIPTEN_RESULT_SUCCESS ==
33 emscripten_set_mousemove_callback("#canvas", this, true,
34 &Animator::MouseCallback));
35 assert(EMSCRIPTEN_RESULT_SUCCESS ==
36 emscripten_set_click_callback("#canvas", this, true,
37 &Animator::MouseCallback));
38 assert(EMSCRIPTEN_RESULT_SUCCESS ==
39 emscripten_set_mousedown_callback("#canvas", this, true,
40 &Animator::MouseCallback));
41 assert(EMSCRIPTEN_RESULT_SUCCESS ==
42 emscripten_set_mouseup_callback("#canvas", this, true,
43 &Animator::MouseCallback));
44 assert(EMSCRIPTEN_RESULT_SUCCESS ==
45 emscripten_set_mouseleave_callback("#canvas", this, true,
46 &Animator::MouseCallback));
47 assert(EMSCRIPTEN_RESULT_SUCCESS ==
48 emscripten_set_mouseenter_callback("#canvas", this, true,
49 &Animator::MouseCallback));
50 assert(EMSCRIPTEN_RESULT_SUCCESS ==
51 emscripten_set_dblclick_callback("#canvas", this, true,
52 &Animator::MouseCallback));
53 assert(EMSCRIPTEN_RESULT_SUCCESS ==
54 emscripten_set_wheel_callback("#canvas", this, true,
55 &Animator::WheelCallback));
56 assert(EMSCRIPTEN_RESULT_SUCCESS ==
57 emscripten_set_keypress_callback("#document", this, true,
58 &Animator::KeyboardCallback));
59 assert(EMSCRIPTEN_RESULT_SUCCESS ==
60 emscripten_set_keydown_callback("#document", this, true,
61 &Animator::KeyboardCallback));
62 assert(EMSCRIPTEN_RESULT_SUCCESS ==
63 emscripten_set_keyup_callback("#document", this, true,
64 &Animator::KeyboardCallback));
65 emscripten_request_animation_frame_loop(&Animator::Redraw, this);
66}
67
68Eigen::Vector2d Animator::MouseCanvasLocation(
69 const EmscriptenMouseEvent &mouse_event) {
70 return {mouse_event.canvasX * 2.0 / canvas_width_ - 1.0,
71 -mouse_event.canvasY * 2.0 / canvas_height_ + 1.0};
72}
73
74Eigen::Vector2d Animator::CanvasToPlotLocation(
75 const Eigen::Vector2d &canvas_loc) {
76 return (canvas_loc - plotter_.GetOffset()).cwiseQuotient(plotter_.GetScale());
77}
78
79void Animator::PrintZoom() {
80 const Eigen::Vector2d upper_right = CanvasToPlotLocation({1.0, 1.0});
81 const Eigen::Vector2d lower_left = CanvasToPlotLocation({-1.0, -1.0});
82 printf("X range is [%f, %f]; Y range is [%f, %f]\n", lower_left.x(),
83 upper_right.x(), lower_left.y(), upper_right.y());
84}
85
86void Animator::PrintPosition(const EmscriptenMouseEvent &mouse_event) {
87 const Eigen::Vector2d mouse_pos =
88 CanvasToPlotLocation(MouseCanvasLocation(mouse_event));
89 printf("Mouse position: (%f, %f)\n", mouse_pos.x(), mouse_pos.y());
90}
91
92void Animator::HandleMouseUp(const EmscriptenMouseEvent &mouse_event) {
93 if (!kZoomButton().IsTransition(mouse_event)) {
94 return;
95 }
96 // We aborted the zoom early for some reason and so shouldn't execute on it:
97 if (!doing_rectangle_zoom_) {
98 return;
99 }
100 const Eigen::Vector2d mouse_up_location = MouseCanvasLocation(mouse_event);
101 doing_rectangle_zoom_ = false;
102 plotter_.ClearZoomRectangle();
103 // The user probably didn't mean to zoom on that click...
104 if ((mouse_up_location - mouse_down_location_).cwiseAbs().minCoeff() < 1e-3) {
105 return;
106 }
107 const Eigen::Vector2d mouse_up_plot_location =
108 CanvasToPlotLocation(mouse_up_location);
109 const Eigen::Vector2d mouse_down_plot_location =
110 CanvasToPlotLocation(mouse_down_location_);
111 SetZoomCorners(mouse_down_plot_location, mouse_up_plot_location);
112}
113
114void Animator::HandleMouseDown(const EmscriptenMouseEvent &mouse_event) {
115 if (kZoomButton().IsTransition(mouse_event)) {
116 mouse_down_location_ = MouseCanvasLocation(mouse_event);
117 doing_rectangle_zoom_ = true;
118 } else if (kPanButton().IsTransition(mouse_event)) {
119 last_pan_mouse_location_ = MouseCanvasLocation(mouse_event);
120 }
121}
122
123void Animator::HandleMouseMove(const EmscriptenMouseEvent &mouse_event) {
124 const Eigen::Vector2d mouse_location = MouseCanvasLocation(mouse_event);
125 if (kPanButton().IsPressed(mouse_event)) {
126 SetFilteredZoom(plotter_.GetScale(), plotter_.GetOffset() + mouse_location -
127 last_pan_mouse_location_);
128 last_pan_mouse_location_ = mouse_location;
129 }
130 if (doing_rectangle_zoom_) {
131 Eigen::Vector2d c1 = CanvasToPlotLocation(mouse_down_location_);
132 Eigen::Vector2d c2 = CanvasToPlotLocation(mouse_location);
133 const Eigen::Vector2d upper_right = CanvasToPlotLocation({1.0, 1.0});
134 const Eigen::Vector2d bottom_left = CanvasToPlotLocation({-1.0, -1.0});
135 if (x_pressed_ && !y_pressed_) {
136 c1.y() = upper_right.y();
137 c2.y() = bottom_left.y();
138 }
139 if (y_pressed_ && !x_pressed_) {
140 c1.x() = upper_right.x();
141 c2.x() = bottom_left.x();
142 }
143 plotter_.SetZoomRectangle(c1, c2);
144 }
145}
146
147void Animator::HandleMouseEnter(const EmscriptenMouseEvent &mouse_event) {
148 // If the zoom button is unclicked and we were zooming, instantly finish the
149 // rectangle zoom.
150 if (doing_rectangle_zoom_ && !kZoomButton().IsPressed(mouse_event)) {
151 plotter_.ClearZoomRectangle();
152 doing_rectangle_zoom_ = false;
153 // Round the current mouse location to the nearest of the four corners.
154 // This is to ensure that a zoom that occurs when the use goes of the edge
155 // of the screen actually goes right up to the edge of the canvas.
156 // Technically, cwiseSign will return zero if you get the mouse enter
157 // event to trigger with the mouse at the center of the screen, but that
158 // seems unlikely.
159 const Eigen::Vector2d canvas_corner =
160 MouseCanvasLocation(mouse_event).cwiseSign();
161 SetZoomCorners(CanvasToPlotLocation(mouse_down_location_),
162 CanvasToPlotLocation(canvas_corner));
163 }
164}
165
166void Animator::SetZoomCorners(const Eigen::Vector2d &c1,
167 const Eigen::Vector2d &c2) {
168 const Eigen::Vector2d scale = ((c2 - c1).cwiseAbs() / 2.0).cwiseInverse();
169 const Eigen::Vector2d offset =
170 Eigen::Vector2d::Ones() - scale.cwiseProduct(c2.cwiseMax(c1));
171 SetFilteredZoom(scale, offset);
172}
173
174void Animator::SetFilteredZoom(Eigen::Vector2d scale, Eigen::Vector2d offset) {
175 if (!x_pressed_ && y_pressed_) {
176 scale.x() = plotter_.GetScale().x();
177 offset.x() = plotter_.GetOffset().x();
178 }
179 if (!y_pressed_ && x_pressed_) {
180 scale.y() = plotter_.GetScale().y();
181 offset.y() = plotter_.GetOffset().y();
182 }
183 plotter_.RecordState();
184 plotter_.SetScale(scale);
185 plotter_.SetOffset(offset);
186 PrintZoom();
187}
188
189void Animator::ResetView() {
190 SetZoomCorners(plotter_.MinValues(), plotter_.MaxValues());
191}
192
193int Animator::Redraw(double time_ms, void *data) {
194 Animator *state = reinterpret_cast<Animator *>(data);
195 state->plotter_.Redraw();
196 return 1;
197}
198
199int Animator::KeyboardCallback(int event_type,
200 const EmscriptenKeyboardEvent *key_event,
201 void *data) {
202 Animator *state = reinterpret_cast<Animator *>(data);
203 const bool key_is_pressed = event_type == EMSCRIPTEN_EVENT_KEYDOWN;
204 if (strncmp(key_event->key, "x", 2) == 0) {
205 state->x_pressed_ = key_is_pressed;
206 return true;
207 } else if (strncmp(key_event->key, "y", 2) == 0) {
208 state->y_pressed_ = key_is_pressed;
209 return true;
210 } else if (strncmp(key_event->key, "z", 2) == 0 && key_event->ctrlKey) {
211 if (event_type == EMSCRIPTEN_EVENT_KEYUP) {
212 state->plotter_.Undo();
213 state->PrintZoom();
214 }
215 return true;
216 } else if (strncmp(key_event->key, "Escape", 7) == 0) {
217 state->doing_rectangle_zoom_ = false;
218 state->plotter_.ClearZoomRectangle();
219 return true;
220 }
221 return false;
222}
223
224int Animator::WheelCallback(int event_type,
225 const EmscriptenWheelEvent *wheel_event,
226 void *data) {
227 Animator *state = reinterpret_cast<Animator *>(data);
228 assert(event_type == EMSCRIPTEN_EVENT_WHEEL);
229 if (wheel_event->deltaMode == DOM_DELTA_PIXEL) {
230 const Eigen::Vector2d canvas_pos =
231 state->MouseCanvasLocation(wheel_event->mouse);
232 constexpr double kWheelTuningScalar = 3.0;
233 const double zoom =
234 -kWheelTuningScalar * wheel_event->deltaY / state->canvas_height_;
235 double zoom_scalar = 1.0 + std::abs(zoom);
236 if (zoom < 0) {
237 zoom_scalar = 1.0 / zoom_scalar;
238 }
239 const Eigen::Vector2d scale = state->plotter_.GetScale() * zoom_scalar;
240 const Eigen::Vector2d offset = (1.0 - zoom_scalar) * canvas_pos +
241 zoom_scalar * state->plotter_.GetOffset();
242 state->SetFilteredZoom(scale, offset);
243 return true;
244 }
245 return false;
246}
247
248int Animator::MouseCallback(int event_type,
249 const EmscriptenMouseEvent *mouse_event,
250 void *data) {
251 Animator *state = reinterpret_cast<Animator *>(data);
252 switch (event_type) {
253 case EMSCRIPTEN_EVENT_CLICK:
254 state->PrintZoom();
255 state->PrintPosition(*mouse_event);
256 return true;
257 break;
258 case EMSCRIPTEN_EVENT_DBLCLICK:
259 state->ResetView();
260 return true;
261 break;
262 case EMSCRIPTEN_EVENT_MOUSEDOWN:
263 state->HandleMouseDown(*mouse_event);
264 return true;
265 break;
266 case EMSCRIPTEN_EVENT_MOUSEUP:
267 state->HandleMouseUp(*mouse_event);
268 return true;
269 break;
270 case EMSCRIPTEN_EVENT_MOUSEMOVE:
271 state->HandleMouseMove(*mouse_event);
272 return true;
273 break;
274 case EMSCRIPTEN_EVENT_MOUSEENTER:
275 state->HandleMouseEnter(*mouse_event);
276 return true;
277 break;
278 }
279 return false;
280}
281
282} // namespace plotting
283} // namespace frc971