Skip to content

Commit 076df41

Browse files
authored
Various Thunderscope visualization improvements (UBC-Thunderbots#3198)
* Add attacker tactic visualizations, thunderscope sim speed control, update debug shapes layer * Add function docs * Move attacker tactic visualization to attacker_tactic.cpp * address comments * Add new autokick/chip graphic and address comment * Remove extra TODO * fix cpp test viz * update debug shapes
1 parent df51988 commit 076df41

22 files changed

Lines changed: 533 additions & 59 deletions

src/proto/message_translation/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ cc_library(
1414
":tbots_geometry",
1515
"//proto:tbots_cc_proto",
1616
"//proto:visualization_cc_proto",
17+
"//software/ai/evaluation:shot",
1718
"//software/ai/navigator/trajectory:bang_bang_trajectory_1d_angular",
1819
"//software/ai/navigator/trajectory:trajectory_path",
1920
"//software/ai/passing:pass_with_rating",

src/proto/message_translation/tbots_protobuf.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -331,14 +331,12 @@ std::unique_ptr<TbotsProto::PlotJugglerValue> createPlotJugglerValue(
331331
return plot_juggler_value_msg;
332332
}
333333

334-
std::unique_ptr<TbotsProto::DebugShapesMap> createDebugShapesMap(
335-
const std::map<std::string, TbotsProto::Shape>& named_shapes)
334+
std::unique_ptr<TbotsProto::DebugShapes> createDebugShapes(
335+
const std::vector<TbotsProto::DebugShapes::DebugShape>& debug_shapes)
336336
{
337-
auto debug_shape_list_msg = std::make_unique<TbotsProto::DebugShapesMap>();
338-
for (auto const& [name, shape_proto] : named_shapes)
339-
{
340-
(*debug_shape_list_msg->mutable_named_shapes())[name] = shape_proto;
341-
}
337+
auto debug_shape_list_msg = std::make_unique<TbotsProto::DebugShapes>();
338+
(*debug_shape_list_msg->mutable_debug_shapes()) = {debug_shapes.begin(),
339+
debug_shapes.end()};
342340
return debug_shape_list_msg;
343341
}
344342

src/proto/message_translation/tbots_protobuf.h

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -127,23 +127,47 @@ std::unique_ptr<TbotsProto::PlotJugglerValue> createPlotJugglerValue(
127127
const std::map<std::string, double>& values);
128128

129129
/**
130-
* Returns a TbotsProto::DebugShapesMap proto containing the name
131-
* shape pairs.
130+
* Returns a TbotsProto::DebugShapes::DebugShape proto for a given shape,
131+
* unique id, and an optional debug text.
132+
*
133+
* @tparam Shape A shape which has a matching createShapeProto function
134+
*
135+
* @param shape The shape to create the protobuf for
136+
* @param unique_id A unique string used to differentiate this shape
137+
* from others. Note that this ID should remain the same for the same shape
138+
* to avoid it from being plotted multiple times.
139+
* @param debug_text An optional string to display on the shape
140+
*
141+
* @return The unique_ptr to a TbotsProto::DebugShapes::DebugShape proto
142+
*/
143+
template <class Shape>
144+
std::unique_ptr<TbotsProto::DebugShapes::DebugShape> createDebugShape(
145+
const Shape& shape, const std::string& unique_id, const std::string& debug_text = "")
146+
{
147+
auto debug_shape = std::make_unique<TbotsProto::DebugShapes::DebugShape>();
148+
(*debug_shape->mutable_shape()) = *createShapeProto(shape);
149+
debug_shape->set_unique_id(unique_id);
150+
debug_shape->set_debug_text(debug_text);
151+
return debug_shape;
152+
};
153+
154+
/**
155+
* Returns a TbotsProto::DebugShapes proto containing the debug shapes
132156
*
133157
* Could use LOG(VISUALIZE) to plot these values. Example:
134-
* LOG(VISUALIZE) << *createDebugShapesMap({
135-
* {"circle_name", *createShapeProto(circle_object)},
136-
* {"stadium_name", *createShapeProto(stadium_object)},
137-
* {"polygon_name", *createShapeProto(polygon_object)}
158+
* LOG(VISUALIZE) << *createDebugShapes({
159+
* *createDebugShape(circle, unique_id1, optional_text),
160+
* *createDebugShape(polygon, unique_id2, optional_text),
161+
* *createDebugShape(stadium, unique_id3, optional_text)
138162
* });
139163
*
140-
* @param named_shapes The map of name shape proto pairs to plot
164+
* @param debug_shapes A list of debug shapes proto to plot
141165
*
142-
* @return The unique_ptr to a TbotsProto::DebugShapesMap proto containing data with
166+
* @return The unique_ptr to a TbotsProto::DebugShapes proto containing data with
143167
* specified names and shapes
144168
*/
145-
std::unique_ptr<TbotsProto::DebugShapesMap> createDebugShapesMap(
146-
const std::map<std::string, TbotsProto::Shape>& named_shapes);
169+
std::unique_ptr<TbotsProto::DebugShapes> createDebugShapes(
170+
const std::vector<TbotsProto::DebugShapes::DebugShape>& debug_shapes);
147171

148172
/**
149173
* Returns a TbotsProto::Shape proto given a shape.

src/proto/visualization.proto

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ message PassVisualization
3333
repeated PassWithRating best_passes = 1;
3434
}
3535

36+
message AttackerVisualization
37+
{
38+
optional Pass pass_ = 1; // needs the _ because pass is a keyword in python
39+
bool pass_committed = 2;
40+
optional Shot shot = 3;
41+
optional Point chip_target = 4;
42+
}
43+
3644
message CostVisualization
3745
{
3846
uint32 num_rows = 1;
@@ -65,7 +73,17 @@ message Shape
6573
}
6674
}
6775

68-
message DebugShapesMap
76+
message DebugShapes
6977
{
70-
map<string, Shape> named_shapes = 1;
78+
message DebugShape
79+
{
80+
Shape shape = 1;
81+
// Unique ID to identify the shape
82+
string unique_id = 2;
83+
// Text to display next to the shape
84+
string debug_text = 3;
85+
}
86+
87+
// Unique ID to a named shape
88+
repeated DebugShape debug_shapes = 1;
7189
}

src/proto/world.proto

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,10 @@ message SimulatorTick
5656

5757
message SimulationState
5858
{
59-
required bool is_playing = 1 [default = true];
59+
required bool is_playing = 1 [default = true];
60+
required double simulation_speed = 2 [default = 1.0];
6061
}
62+
6163
message Pass
6264
{
6365
// The location of the passer
@@ -70,6 +72,13 @@ message Pass
7072
required double pass_speed_m_per_s = 3;
7173
}
7274

75+
message Shot
76+
{
77+
required Point shot_origin = 1;
78+
required Point shot_target = 2;
79+
required Angle open_angle = 3;
80+
}
81+
7382
message EnemyThreat
7483
{
7584
// The enemy robot

src/software/ai/hl/stp/tactic/attacker/attacker_tactic.cpp

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "software/ai/hl/stp/tactic/attacker/attacker_tactic.h"
22

3+
#include "proto/message_translation/tbots_geometry.h"
34
#include "shared/constants.h"
45
#include "software/ai/evaluation/calc_best_shot.h"
56
#include "software/logger/logger.h"
@@ -67,4 +68,44 @@ void AttackerTactic::updatePrimitive(const TacticUpdate& tactic_update, bool res
6768

6869
fsm_map.at(tactic_update.robot.id())
6970
->process_event(AttackerFSM::Update(control_params, tactic_update));
71+
72+
visualizeControlParams(*tactic_update.world_ptr, control_params);
73+
}
74+
75+
void AttackerTactic::visualizeControlParams(
76+
const World& world, const AttackerFSM::ControlParams& control_params)
77+
{
78+
TbotsProto::AttackerVisualization pass_visualization_msg;
79+
80+
if (control_params.best_pass_so_far.has_value())
81+
{
82+
TbotsProto::Pass pass_msg;
83+
*(pass_msg.mutable_passer_point()) =
84+
*createPointProto(control_params.best_pass_so_far->passerPoint());
85+
*(pass_msg.mutable_receiver_point()) =
86+
*createPointProto(control_params.best_pass_so_far->receiverPoint());
87+
pass_msg.set_pass_speed_m_per_s(control_params.best_pass_so_far->speed());
88+
*(pass_visualization_msg.mutable_pass_()) = pass_msg;
89+
}
90+
91+
pass_visualization_msg.set_pass_committed(pass_committed);
92+
93+
if (control_params.shot.has_value())
94+
{
95+
TbotsProto::Shot shot_msg;
96+
*(shot_msg.mutable_shot_origin()) = *createPointProto(world.ball().position());
97+
*(shot_msg.mutable_shot_target()) =
98+
*createPointProto(control_params.shot->getPointToShootAt());
99+
*(shot_msg.mutable_open_angle()) =
100+
*createAngleProto(control_params.shot->getOpenAngle());
101+
*(pass_visualization_msg.mutable_shot()) = shot_msg;
102+
}
103+
104+
if (chip_target.has_value())
105+
{
106+
*(pass_visualization_msg.mutable_chip_target()) =
107+
*createPointProto(chip_target.value());
108+
}
109+
110+
LOG(VISUALIZE) << pass_visualization_msg;
70111
}

src/software/ai/hl/stp/tactic/attacker/attacker_tactic.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,15 @@ class AttackerTactic : public Tactic
4646
private:
4747
void updatePrimitive(const TacticUpdate& tactic_update, bool reset_fsm) override;
4848

49+
/**
50+
* Log visualize the control parameters for this AttackerTactic
51+
*
52+
* @param world Current state of the world
53+
* @param control_params The control parameters to visualize
54+
*/
55+
void visualizeControlParams(const World& world,
56+
const AttackerFSM::ControlParams& control_params);
57+
4958
std::map<RobotId, std::unique_ptr<FSM<AttackerFSM>>> fsm_map;
5059

5160
// The pass to execute

src/software/thunderscope/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ py_library(
7777
"//software/thunderscope/common:proto_configuration_widget",
7878
"//software/thunderscope/common:proto_plotter",
7979
"//software/thunderscope/gl:gl_widget",
80+
"//software/thunderscope/gl/layers:gl_attacker_layer",
8081
"//software/thunderscope/gl/layers:gl_cost_vis_layer",
8182
"//software/thunderscope/gl/layers:gl_debug_shapes_layer",
8283
"//software/thunderscope/gl/layers:gl_obstacle_layer",

src/software/thunderscope/binary_context_managers/full_system.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,11 +170,12 @@ def setup_proto_unix_io(self, proto_unix_io: ProtoUnixIO) -> None:
170170
for proto_class in [
171171
PathVisualization,
172172
PassVisualization,
173+
AttackerVisualization,
173174
CostVisualization,
174175
NamedValue,
175176
PlayInfo,
176177
ObstacleList,
177-
DebugShapesMap,
178+
DebugShapes,
178179
]:
179180
proto_unix_io.attach_unix_receiver(
180181
runtime_dir=self.full_system_runtime_dir,

src/software/thunderscope/constants.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from pyqtgraph.Qt import QtCore, QtGui
2+
from OpenGL.GL import *
23
from proto.import_all_protos import *
34
from enum import Enum, IntEnum
45
from proto.robot_log_msg_pb2 import LogLevel
@@ -159,6 +160,8 @@ class EstopMode(IntEnum):
159160
f"{SAVED_LAYOUT_PATH}/last_opened_tscope_layout.{LAYOUT_FILE_EXTENSION}"
160161
)
161162

163+
SIMULATION_SPEEDS = [2, 1, 0.5, 0.2, 0.1, 0.05]
164+
162165
THUNDERSCOPE_HELP_TEXT = textwrap.dedent(
163166
f"""
164167
<h3>General Controls</h3><br>
@@ -167,6 +170,8 @@ class EstopMode(IntEnum):
167170
<b><code>M:</code></b> Toggle measure mode<br>
168171
<b><code>S:</code></b> Toggle visibility of robot/ball speed visualization<br>
169172
<b><code>Ctrl + Space:</code></b> Stop AI vs AI simulation<br>
173+
<b><code>Ctrl + Up:</code></b> Increment simulation speed<br>
174+
<b><code>Ctrl + Down:</code></b> Decrement simulation speed<br>
170175
<b><code>Number Keys:</code></b> Position camera to preset view<br>
171176
<b><code>Shift + Left Click:</code></b> Place the ball at the cursor<br>
172177
<b><code>Shift + Left Click Drag:</code></b> Place the ball at the cursor and kick it<br>
@@ -277,7 +282,13 @@ class Colors(object):
277282
NAVIGATOR_OBSTACLE_COLOR = QtGui.QColor(255, 80, 0, 100)
278283
DEBUG_SHAPES_COLOR = QtGui.QColor(190, 50, 235, 255)
279284
PASS_VISUALIZATION_COLOR = QtGui.QColor(255, 0, 0, 80)
285+
UNCOMMITTED_PASS_VISUALIZATION_COLOR = QtGui.QColor(255, 0, 0, 80)
286+
COMMITTED_PASS_VISUALIZATION_COLOR = QtGui.QColor(0, 255, 255, 255)
287+
SHOT_VISUALIZATION_COLOR = QtGui.QColor(255, 0, 0, 255)
288+
CHIP_TARGET_VISUALIZATION_COLOR = QtGui.QColor(255, 0, 0, 255)
280289
BREAKBEAM_TRIPPED_COLOR = QtGui.QColor(255, 0, 0, 255)
290+
AUTO_CHIP_ENABLED_COLOR = QtGui.QColor(215, 0, 200, 255)
291+
AUTO_KICK_ENABLED_COLOR = QtGui.QColor(255, 0, 0, 255)
281292

282293
VALIDATION_PASSED_COLOR = QtGui.QColor(0, 200, 0, 255)
283294
VALIDATION_FAILED_COLOR = QtGui.QColor(200, 0, 0, 255)
@@ -323,3 +334,21 @@ class TrailValues:
323334

324335
DEFAULT_TRAIL_LENGTH = 20
325336
DEFAULT_TRAIL_SAMPLING_RATE = 0
337+
338+
339+
class CustomGLOptions:
340+
"""
341+
Custom OpenGL Rendering modes that could be used in addition to
342+
the ones provided by PyQtGraph in GLGraphicsItem.py GLOptions.
343+
"""
344+
345+
# Opaque rendering (i.e. overlapping colors are not blended) while
346+
# also allowing for custom depth values to be set.
347+
# This is useful when the graphics are overlaid on top of (e.g.) a
348+
# yellow robot where the blended colors would not be easily visible.
349+
OPAQUE_WITH_OUT_DEPTH_TEST = {
350+
GL_DEPTH_TEST: False,
351+
GL_BLEND: False,
352+
GL_ALPHA_TEST: False,
353+
GL_CULL_FACE: False,
354+
}

0 commit comments

Comments
 (0)