forked from RobotLocomotion/drake
-
Notifications
You must be signed in to change notification settings - Fork 0
/
meshcat_visualizer.cc
320 lines (281 loc) · 11.8 KB
/
meshcat_visualizer.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
#include "drake/geometry/meshcat_visualizer.h"
#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <fmt/format.h>
#include "drake/common/extract_double.h"
#include "drake/common/overloaded.h"
#include "drake/geometry/meshcat_graphviz.h"
#include "drake/geometry/meshcat_internal.h"
#include "drake/geometry/proximity/polygon_to_triangle_mesh.h"
#include "drake/geometry/proximity/volume_to_surface_mesh.h"
#include "drake/geometry/utilities.h"
namespace drake {
namespace geometry {
template <typename T>
MeshcatVisualizer<T>::MeshcatVisualizer(std::shared_ptr<Meshcat> meshcat,
MeshcatVisualizerParams params)
: systems::LeafSystem<T>(systems::SystemTypeTag<MeshcatVisualizer>{}),
meshcat_(std::move(meshcat)),
params_(std::move(params)),
alpha_slider_name_(std::string(params_.prefix + " α")) {
DRAKE_DEMAND(meshcat_ != nullptr);
DRAKE_DEMAND(params_.publish_period >= 0.0);
if (params_.role == Role::kUnassigned) {
throw std::runtime_error(
"MeshcatVisualizer cannot be used for geometries with the "
"Role::kUnassigned value. Please choose kProximity, kPerception, or "
"kIllustration");
}
this->DeclarePeriodicPublishEvent(params_.publish_period, 0.0,
&MeshcatVisualizer<T>::UpdateMeshcat);
this->DeclareForcedPublishEvent(&MeshcatVisualizer<T>::UpdateMeshcat);
if (params_.delete_on_initialization_event) {
this->DeclareInitializationPublishEvent(
&MeshcatVisualizer<T>::OnInitialization);
}
query_object_input_port_ =
this->DeclareAbstractInputPort("query_object", Value<QueryObject<T>>())
.get_index();
if (params_.enable_alpha_slider) {
alpha_value_ = params_.initial_alpha_slider_value;
meshcat_->AddSlider(alpha_slider_name_, 0.02, 1.0, 0.02, alpha_value_);
}
}
template <typename T>
template <typename U>
MeshcatVisualizer<T>::MeshcatVisualizer(const MeshcatVisualizer<U>& other)
: MeshcatVisualizer(other.meshcat_, other.params_) {}
template <typename T>
MeshcatVisualizer<T>::~MeshcatVisualizer() = default;
template <typename T>
void MeshcatVisualizer<T>::Delete() const {
meshcat_->Delete(params_.prefix);
version_ = std::nullopt;
}
template <typename T>
MeshcatAnimation* MeshcatVisualizer<T>::StartRecording(
bool set_transforms_while_recording) {
meshcat_->StartRecording(1.0 / params_.publish_period,
set_transforms_while_recording);
return &meshcat_->get_mutable_recording();
}
template <typename T>
void MeshcatVisualizer<T>::StopRecording() {
meshcat_->StopRecording();
}
template <typename T>
void MeshcatVisualizer<T>::PublishRecording() const {
meshcat_->PublishRecording();
}
template <typename T>
void MeshcatVisualizer<T>::DeleteRecording() {
meshcat_->DeleteRecording();
}
template <typename T>
MeshcatAnimation* MeshcatVisualizer<T>::get_mutable_recording() {
return &meshcat_->get_mutable_recording();
}
template <typename T>
MeshcatVisualizer<T>& MeshcatVisualizer<T>::AddToBuilder(
systems::DiagramBuilder<T>* builder, const SceneGraph<T>& scene_graph,
std::shared_ptr<Meshcat> meshcat, MeshcatVisualizerParams params) {
return AddToBuilder(builder, scene_graph.get_query_output_port(),
std::move(meshcat), std::move(params));
}
template <typename T>
MeshcatVisualizer<T>& MeshcatVisualizer<T>::AddToBuilder(
systems::DiagramBuilder<T>* builder,
const systems::OutputPort<T>& query_object_port,
std::shared_ptr<Meshcat> meshcat, MeshcatVisualizerParams params) {
const std::string aspirational_name =
fmt::format("meshcat_visualizer({})", params.prefix);
auto& visualizer = *builder->template AddSystem<MeshcatVisualizer<T>>(
std::move(meshcat), std::move(params));
if (!builder->HasSubsystemNamed(aspirational_name)) {
visualizer.set_name(aspirational_name);
}
builder->Connect(query_object_port, visualizer.query_object_input_port());
return visualizer;
}
template <typename T>
systems::EventStatus MeshcatVisualizer<T>::UpdateMeshcat(
const systems::Context<T>& context) const {
const auto& query_object =
query_object_input_port().template Eval<QueryObject<T>>(context);
const GeometryVersion& current_version =
query_object.inspector().geometry_version();
if (!version_.has_value()) {
// When our current version is null, that means we haven't added any
// geometry to Meshcat yet, which means we also need to establish our
// default visibility just prior to sending the geometry.
meshcat_->SetProperty(params_.prefix, "visible",
params_.visible_by_default);
}
if (!version_.has_value() ||
!version_->IsSameAs(current_version, params_.role)) {
SetObjects(query_object.inspector());
SetAlphas(/* initializing = */ true);
version_ = current_version;
}
SetTransforms(context, query_object);
if (params_.enable_alpha_slider) {
double new_alpha_value = meshcat_->GetSliderValue(alpha_slider_name_);
if (new_alpha_value != alpha_value_) {
alpha_value_ = new_alpha_value;
SetAlphas(/* initializing = */ false);
}
}
meshcat_->SetSimulationTime(ExtractDoubleOrThrow(context.get_time()));
return systems::EventStatus::Succeeded();
}
template <typename T>
void MeshcatVisualizer<T>::SetObjects(
const SceneGraphInspector<T>& inspector) const {
// Frames registered previously that are not set again here should be deleted.
std::map<FrameId, std::string> frames_to_delete{};
dynamic_frames_.swap(frames_to_delete);
// Geometries registered previously that are not set again here should be
// deleted.
std::map<GeometryId, std::string> geometries_to_delete{};
geometries_.swap(geometries_to_delete);
// TODO(SeanCurtis-TRI): Mimic the full tree structure in SceneGraph.
// SceneGraph supports arbitrary hierarchies of frames just like Meshcat.
// This code is arbitrarily flattening it because the current SceneGraph API
// is insufficient to support walking the tree.
for (FrameId frame_id : inspector.GetAllFrameIds()) {
std::string frame_path =
frame_id == inspector.world_frame_id()
? params_.prefix
: fmt::format("{}/{}", params_.prefix, inspector.GetName(frame_id));
// MultibodyPlant declares frames with SceneGraph using "::". We replace
// those with `/` here to expose the full tree to Meshcat.
size_t pos = 0;
while ((pos = frame_path.find("::", pos)) != std::string::npos) {
frame_path.replace(pos++, 2, "/");
}
bool frame_has_any_geometry = false;
for (GeometryId geom_id : inspector.GetGeometries(frame_id, params_.role)) {
const GeometryProperties& properties =
*inspector.GetProperties(geom_id, params_.role);
if (properties.HasProperty("meshcat", "accepting")) {
if (properties.GetProperty<std::string>("meshcat", "accepting") !=
params_.prefix) {
continue;
}
} else if (!params_.include_unspecified_accepting) {
continue;
}
// We'll turn scoped names into meshcat paths.
const std::string geometry_name =
internal::TransformGeometryName(geom_id, inspector);
const std::string path = fmt::format("{}/{}", frame_path, geometry_name);
const Rgba rgba = properties.GetPropertyOrDefault("phong", "diffuse",
params_.default_color);
// The "object" will typically be the geometry's shape. But, for the
// proximity role, we prefer, first, the hydroelastic surface if
// available, or, second, the convex hull. Record if we've used one of
// those proxies.
bool geometry_already_set = false;
if constexpr (std::is_same_v<T, double>) {
if (params_.show_hydroelastic) {
auto maybe_mesh = inspector.maybe_get_hydroelastic_mesh(geom_id);
std::visit<void>(
overloaded{[](std::monostate) {},
[&](const TriangleSurfaceMesh<double>* mesh) {
DRAKE_DEMAND(mesh != nullptr);
meshcat_->SetObject(path, *mesh, rgba);
geometry_already_set = true;
},
[&](const VolumeMesh<double>* mesh) {
DRAKE_DEMAND(mesh != nullptr);
meshcat_->SetObject(
path, ConvertVolumeToSurfaceMesh(*mesh), rgba);
geometry_already_set = true;
}},
maybe_mesh);
}
}
// Proximity role favors convex hulls if available.
if (const PolygonSurfaceMesh<double>* hull = nullptr;
(!geometry_already_set) &&
(params_.role == Role::kProximity) &&
(hull = inspector.GetConvexHull(geom_id))) {
// Convert polygonal surface mesh to triangle surface mesh.
const TriangleSurfaceMesh<double> tri_hull =
internal::MakeTriangleFromPolygonMesh(*hull);
meshcat_->SetObject(path, tri_hull, rgba);
geometry_already_set = true;
}
if (!geometry_already_set) {
meshcat_->SetObject(path, inspector.GetShape(geom_id), rgba);
}
meshcat_->SetTransform(path, inspector.GetPoseInFrame(geom_id));
geometries_[geom_id] = path;
geometries_to_delete.erase(geom_id); // Don't delete this one.
frame_has_any_geometry = true;
}
if (frame_has_any_geometry && (frame_id != inspector.world_frame_id())) {
dynamic_frames_[frame_id] = frame_path;
frames_to_delete.erase(frame_id); // Don't delete this one.
}
}
for (const auto& [geom_id, path] : geometries_to_delete) {
unused(geom_id);
meshcat_->Delete(path);
}
for (const auto& [frame_id, path] : frames_to_delete) {
unused(frame_id);
meshcat_->Delete(path);
}
}
template <typename T>
void MeshcatVisualizer<T>::SetTransforms(
const systems::Context<T>& context,
const QueryObject<T>& query_object) const {
for (const auto& [frame_id, path] : dynamic_frames_) {
const math::RigidTransformd X_WF =
internal::convert_to_double(query_object.GetPoseInWorld(frame_id));
meshcat_->SetTransform(path, X_WF,
ExtractDoubleOrThrow(context.get_time()));
}
}
template <typename T>
void MeshcatVisualizer<T>::SetAlphas(bool initializing) const {
if (initializing) {
for (const auto& [_, geo_path] : geometries_) {
meshcat_->SetProperty(geo_path, "modulated_opacity", alpha_value_);
}
} else {
// The geometries visualized by this visualizer (stored in geometries_) all
// have a common prefix and for a well-configured visualizer, it is a
// *unique* prefix. So, we can rely on meshcat's behavior to update all
// materials in a tree with a single invocation on the root path. This
// requires that all object instantiations are complete in the visualizer
// instance.
meshcat_->SetProperty(params_.prefix, "modulated_opacity", alpha_value_);
}
}
template <typename T>
systems::EventStatus MeshcatVisualizer<T>::OnInitialization(
const systems::Context<T>&) const {
Delete();
return systems::EventStatus::Succeeded();
}
template <typename T>
typename systems::LeafSystem<T>::GraphvizFragment
MeshcatVisualizer<T>::DoGetGraphvizFragment(
const typename systems::LeafSystem<T>::GraphvizFragmentParams& params)
const {
internal::MeshcatGraphviz meshcat_graphviz(params_.prefix,
/* subscribe = */ false);
return meshcat_graphviz.DecorateResult(
systems::LeafSystem<T>::DoGetGraphvizFragment(
meshcat_graphviz.DecorateParams(params)));
}
} // namespace geometry
} // namespace drake
DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_NONSYMBOLIC_SCALARS(
class ::drake::geometry::MeshcatVisualizer);