ilqgames
A new real-time solver for large-scale differential games.
top_down_renderer.cpp
1 /*
2  * Copyright (c) 2019, The Regents of the University of California (Regents).
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * 1. Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *
12  * 2. Redistributions in binary form must reproduce the above
13  * copyright notice, this list of conditions and the following
14  * disclaimer in the documentation and/or other materials provided
15  * with the distribution.
16  *
17  * 3. Neither the name of the copyright holder nor the names of its
18  * contributors may be used to endorse or promote products derived
19  * from this software without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS AS IS
22  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  *
33  * Please contact the author(s) of this library if you have any questions.
34  * Authors: David Fridovich-Keil ( dfk@eecs.berkeley.edu )
35  */
36 
37 ///////////////////////////////////////////////////////////////////////////////
38 //
39 // Core renderer for 2D top-down trajectories. Integrates with DearImGui.
40 //
41 ///////////////////////////////////////////////////////////////////////////////
42 
43 #include <ilqgames/gui/control_sliders.h>
44 #include <ilqgames/gui/top_down_renderer.h>
45 #include <ilqgames/utils/operating_point.h>
46 #include <ilqgames/utils/solver_log.h>
47 #include <ilqgames/utils/types.h>
48 
49 #include <imgui/imgui.h>
50 #include <math.h>
51 #include <algorithm>
52 #include <vector>
53 
54 namespace ilqgames {
55 
56 namespace {
57 // Zoom parameters.
58 static constexpr float kPixelsToZoomConversion = 1.0 / 20.0;
59 static constexpr float kMinZoom = 2.0;
60 } // anonymous namespace
61 
62 void TopDownRenderer::Render() {
63  // Extract current log.
64  const auto& logs = sliders_->LogForEachProblem();
65 
66  // Do nothing if no iterates yet.
67  if (sliders_->MaxLogIndex() == 1) return;
68 
69  // Get the number of agents in each problem.
70  std::vector<size_t> num_agents(problems_.size());
71  std::transform(
72  problems_.begin(), problems_.end(), num_agents.begin(),
73  [](const std::shared_ptr<const TopDownRenderableProblem>& problem) {
74  return problem->Dynamics()->NumPlayers();
75  });
76 
77  // Set up main top-down viewer window.
78  ImGui::Begin("Top-Down View");
79 
80  // Set up child window displaying key codes for navigation and zoom,
81  // as well as mouse position.
82  const ImVec2 mouse_position = ImGui::GetMousePos();
83  if (ImGui::BeginChild("User Guide")) {
84  ImGui::TextUnformatted("Press \"c\" key to enable navigation.");
85  ImGui::TextUnformatted("Press \"z\" key to change zoom.");
86 
87  const Point2 mouse_point = WindowCoordinatesToPosition(mouse_position);
88  ImGui::Text("Mouse is at: (%3.1f, %3.1f)", mouse_point.x(),
89  mouse_point.y());
90  }
91  ImGui::EndChild();
92 
93  // Update last mouse position if "c" or "z" key was just pressed.
94  constexpr bool kCatchRepeats = false;
95  if (ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_C], kCatchRepeats) ||
96  ImGui::IsKeyPressed(ImGui::GetIO().KeyMap[ImGuiKey_Z], kCatchRepeats))
97  last_mouse_position_ = mouse_position;
98  else if (ImGui::IsKeyReleased(ImGui::GetIO().KeyMap[ImGuiKey_C])) {
99  // When "c" is released, update center delta.
100  center_delta_.x +=
101  PixelsToLength(mouse_position.x - last_mouse_position_.x);
102  center_delta_.y -=
103  PixelsToLength(mouse_position.y - last_mouse_position_.y);
104  } else if (ImGui::IsKeyReleased(ImGui::GetIO().KeyMap[ImGuiKey_Z])) {
105  // When "z" is released, update pixel to meter ratio.
106  const float mouse_delta_y = mouse_position.y - last_mouse_position_.y;
107  pixel_to_meter_ratio_ =
108  std::max(kMinZoom, pixel_to_meter_ratio_ -
109  kPixelsToZoomConversion * mouse_delta_y);
110  }
111 
112  // Get the draw list for this window.
113  ImDrawList* draw_list = ImGui::GetWindowDrawList();
114  const ImU32 trajectory_color = ImColor(ImVec4(1.0, 1.0, 1.0, 0.5));
115  const float trajectory_thickness = std::min(1.0f, LengthToPixels(0.5));
116 
117  // Loop over all problems and render one at a time.
118  for (size_t problem_idx = 0; problem_idx < problems_.size(); problem_idx++) {
119  const auto& problem = problems_[problem_idx];
120  const auto& log = logs[problem_idx];
121 
122  // (1) Draw this trajectory iterate.
123  std::vector<ImVec2> points(time::kNumTimeSteps);
124  for (size_t ii = 0; ii < num_agents[problem_idx]; ii++) {
125  for (size_t kk = 0; kk < time::kNumTimeSteps; kk++) {
126  const VectorXf x = log->State(sliders_->SolverIterate(problem_idx), kk);
127  points[kk] =
128  PositionToWindowCoordinates(problem->Xs(x)[ii], problem->Ys(x)[ii]);
129  }
130 
131  constexpr bool kPolylineIsClosed = false;
132  draw_list->AddPolyline(points.data(), time::kNumTimeSteps,
133  trajectory_color, kPolylineIsClosed,
134  trajectory_thickness);
135  }
136 
137  // Agent colors will all be greenish. Also specify circle radius and
138  // triangle base and height (in pixels).
139  constexpr float kMinGreen = 0.15;
140  constexpr float kMaxGreen = 1.0 - kMinGreen;
141  const float color_scaling = (1.0 - 2.0 * kMinGreen) *
142  static_cast<float>(problem_idx + 1) /
143  static_cast<float>(problems_.size());
144 
145  const ImU32 agent_color =
146  ImColor(ImVec4(0.15, kMinGreen + color_scaling,
147  kMinGreen + kMaxGreen - color_scaling, 1.0));
148  const float agent_radius = std::max(5.0f, LengthToPixels(2.5));
149  const float agent_base = std::max(6.0f, LengthToPixels(2.5));
150  const float agent_height = std::max(10.0f, LengthToPixels(3.0));
151 
152  // Draw each position as either an isosceles triangle (if heading idx is
153  // >= 0) or a circle (if heading idx < 0).
154  const VectorXf current_x =
155  log->InterpolateState(sliders_->SolverIterate(problem_idx),
156  sliders_->InterpolationTime(problem_idx));
157  const std::vector<float> current_pxs = problem->Xs(current_x);
158  const std::vector<float> current_pys = problem->Ys(current_x);
159  const std::vector<float> current_thetas = problem->Thetas(current_x);
160  for (size_t ii = 0; ii < num_agents[problem_idx]; ii++) {
161  const ImVec2 p =
162  PositionToWindowCoordinates(current_pxs[ii], current_pys[ii]);
163 
164  if (ii >= current_thetas.size()) {
165  VLOG(2) << "More players than coordinates to visualize.";
166  break;
167  } else {
168  const float heading = HeadingToWindowCoordinates(current_thetas[ii]);
169  const float cheading = std::cos(heading);
170  const float sheading = std::sin(heading);
171 
172  // Triangle vertices (top, bottom left, bottom right in Frenet frame).
173  // NOTE: this may not be in CCW order. Not sure if that matters.
174  const ImVec2 top(p.x + agent_height * cheading,
175  p.y + agent_height * sheading);
176  const ImVec2 bl(p.x - 0.5 * agent_base * sheading,
177  p.y + 0.5 * agent_base * cheading);
178  const ImVec2 br(p.x + 0.5 * agent_base * sheading,
179  p.y - 0.5 * agent_base * cheading);
180 
181  draw_list->AddTriangleFilled(bl, br, top, agent_color);
182  draw_list->AddCircle(p, agent_radius, agent_color);
183  }
184  }
185  }
186 
187  ImGui::End();
188 }
189 
190 inline float TopDownRenderer::CurrentZoomLevel() const {
191  float conversion = pixel_to_meter_ratio_;
192 
193  // Handle "z" down case.
194  if (ImGui::IsKeyDown(ImGui::GetIO().KeyMap[ImGuiKey_Z])) {
195  const float mouse_delta_y = ImGui::GetMousePos().y - last_mouse_position_.y;
196  conversion = std::max(kMinZoom,
197  conversion - kPixelsToZoomConversion * mouse_delta_y);
198  }
199 
200  return conversion;
201 }
202 
203 inline ImVec2 TopDownRenderer::PositionToWindowCoordinates(float x,
204  float y) const {
205  ImVec2 coords = WindowCenter();
206 
207  // Offsets if "c" key is currently down.
208  if (ImGui::IsKeyDown(ImGui::GetIO().KeyMap[ImGuiKey_C])) {
209  const ImVec2 mouse_position = ImGui::GetMousePos();
210  x += PixelsToLength(mouse_position.x - last_mouse_position_.x);
211  y -= PixelsToLength(mouse_position.y - last_mouse_position_.y);
212  }
213 
214  coords.x += LengthToPixels(x + center_delta_.x);
215  coords.y -= LengthToPixels(y + center_delta_.y);
216  return coords;
217 }
218 
219 inline Point2 TopDownRenderer::WindowCoordinatesToPosition(
220  const ImVec2& coords) const {
221  const ImVec2 center = WindowCenter();
222 
223  // NOTE: only correct when "c" key is not down.
224  const float x = PixelsToLength(coords.x - center.x) - center_delta_.x;
225  const float y = PixelsToLength(center.y - coords.y) - center_delta_.y;
226  return Point2(x, y);
227 }
228 
229 inline ImVec2 TopDownRenderer::WindowCenter() const {
230  const ImVec2 window_pos = ImGui::GetWindowPos();
231  const float window_width = ImGui::GetWindowWidth();
232  const float window_height = ImGui::GetWindowHeight();
233 
234  const float center_x = window_pos.x + 0.5 * window_width;
235  const float center_y = window_pos.y + 0.5 * window_height;
236  return ImVec2(center_x, center_y);
237 }
238 
239 } // namespace ilqgames