-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
hydroelastic_callback.h
404 lines (348 loc) · 16.9 KB
/
hydroelastic_callback.h
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
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
#pragma once
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
#include <fcl/fcl.h>
#include <fmt/format.h>
#include "drake/common/drake_export.h"
#include "drake/common/eigen_types.h"
#include "drake/geometry/geometry_ids.h"
#include "drake/geometry/proximity/collision_filter.h"
#include "drake/geometry/proximity/field_intersection.h"
#include "drake/geometry/proximity/hydroelastic_internal.h"
#include "drake/geometry/proximity/mesh_half_space_intersection.h"
#include "drake/geometry/proximity/mesh_intersection.h"
#include "drake/geometry/proximity/mesh_plane_intersection.h"
#include "drake/geometry/proximity/penetration_as_point_pair_callback.h"
#include "drake/geometry/proximity/proximity_utilities.h"
#include "drake/geometry/query_results/contact_surface.h"
#include "drake/geometry/query_results/penetration_as_point_pair.h"
#include "drake/math/rigid_transform.h"
#include "drake/math/rotation_matrix.h"
namespace drake {
namespace geometry {
namespace internal {
namespace hydroelastic DRAKE_NO_EXPORT {
/* Supporting data for the shape-to-shape hydroelastic contact callback (see
Callback below). It includes:
- A collision filter instance.
- The T-valued poses of _all_ geometries in the corresponding SceneGraph,
each indexed by its corresponding geometry's GeometryId.
- The representation of all geometries that have been prepped for computing
contact surfaces.
- The choice of how to represent contact polygons.
- A vector of contact surfaces -- one instance of ContactSurface for
every supported, unfiltered penetrating pair.
@tparam T The computation scalar. */
template <typename T>
struct CallbackData {
/* Constructs the fully-specified callback data. The values are as described
in the class documentation. All parameters are aliased in the data and must
remain valid at least as long as the CallbackData instance.
@param collision_filter_in The collision filter system. Aliased.
@param X_WGs_in The T-valued poses. Aliased.
@param geometries_in The set of all hydroelastic geometric
representations. Aliased.
@param representation Controls the mesh representation of
the contact surface. See
@ref contact_surface_discrete_representation
"contact surface representation" for more
details.
@param surfaces_in The output results. Aliased. */
CallbackData(
const CollisionFilter* collision_filter_in,
const std::unordered_map<GeometryId, math::RigidTransform<T>>* X_WGs_in,
const Geometries* geometries_in,
HydroelasticContactRepresentation representation_in,
std::vector<ContactSurface<T>>* surfaces_in)
: collision_filter(*collision_filter_in),
X_WGs(*X_WGs_in),
geometries(*geometries_in),
representation(representation_in),
surfaces(*surfaces_in) {
DRAKE_DEMAND(collision_filter_in != nullptr);
DRAKE_DEMAND(X_WGs_in != nullptr);
DRAKE_DEMAND(geometries_in != nullptr);
DRAKE_DEMAND(surfaces_in != nullptr);
}
/* The collision filter system. */
const CollisionFilter& collision_filter;
/* The T-valued poses of all geometries. */
const std::unordered_map<GeometryId, math::RigidTransform<T>>& X_WGs;
/* The hydroelastic geometric representations. */
const Geometries& geometries;
/* The requested mesh representation type. */
const HydroelasticContactRepresentation representation;
/* The results of the distance query. */
std::vector<ContactSurface<T>>& surfaces;
};
enum class CalcContactSurfaceResult {
kCalculated, //< Computation was successful; a contact surface is
//< only produced if the objects were in contact.
kUnsupported, //< Contact surface can't be computed for the geometry
//< pair.
kHalfSpaceHalfSpace, //< Contact between two half spaces; not allowed.
kRigidRigid, //< Contact between two rigid geometries; not allowed.
kCompliantHalfSpaceCompliantMesh, //< Contact between a compliant mesh and a
//< compliant half space; not allowed.
};
/* Computes ContactSurface using the algorithm appropriate to the Shape types
represented by the given `soft` and `rigid` geometries.
@pre The geometries are not *both* half spaces. */
template <typename T>
std::unique_ptr<ContactSurface<T>> DispatchRigidSoftCalculation(
const SoftGeometry& soft, const math::RigidTransform<T>& X_WS,
GeometryId id_S, const RigidGeometry& rigid,
const math::RigidTransform<T>& X_WR, GeometryId id_R,
HydroelasticContactRepresentation representation) {
if (soft.is_half_space() || rigid.is_half_space()) {
if (soft.is_half_space()) {
DRAKE_DEMAND(!rigid.is_half_space());
// Soft half space with rigid mesh.
const TriangleSurfaceMesh<double>& mesh_R = rigid.mesh();
const Bvh<Obb, TriangleSurfaceMesh<double>>& bvh_R = rigid.bvh();
return ComputeContactSurfaceFromSoftHalfSpaceRigidMesh(
id_S, X_WS, soft.pressure_scale(), id_R, mesh_R, bvh_R, X_WR,
representation);
} else {
// Soft volume vs rigid half space.
const VolumeMeshFieldLinear<double, double>& field_S =
soft.pressure_field();
const Bvh<Obb, VolumeMesh<double>>& bvh_S = soft.bvh();
return ComputeContactSurfaceFromSoftVolumeRigidHalfSpace(
id_S, field_S, bvh_S, X_WS, id_R, X_WR, representation);
}
} else {
// soft cannot be a half space; so this must be mesh-mesh.
const VolumeMeshFieldLinear<double, double>& field_S =
soft.pressure_field();
const Bvh<Obb, VolumeMesh<double>>& bvh_S = soft.bvh();
const TriangleSurfaceMesh<double>& mesh_R = rigid.mesh();
const Bvh<Obb, TriangleSurfaceMesh<double>>& bvh_R = rigid.bvh();
return ComputeContactSurfaceFromSoftVolumeRigidSurface(
id_S, field_S, bvh_S, X_WS, id_R, mesh_R, bvh_R, X_WR, representation);
}
}
/* Computes ContactSurface using the algorithm appropriate to the Shape types
represented by the given `compliant` geometries.
@pre None of the geometries are half spaces. */
template <typename T>
std::unique_ptr<ContactSurface<T>> DispatchCompliantCompliantCalculation(
const SoftGeometry& compliant0_F, const math::RigidTransform<T>& X_WF,
GeometryId id0, const SoftGeometry& compliant1_G,
const math::RigidTransform<T>& X_WG, GeometryId id1,
HydroelasticContactRepresentation representation) {
DRAKE_DEMAND(!compliant0_F.is_half_space() && !compliant1_G.is_half_space());
const VolumeMeshFieldLinear<double, double>& field0_F =
compliant0_F.pressure_field();
const Bvh<Obb, VolumeMesh<double>>& bvh0_F = compliant0_F.bvh();
const VolumeMeshFieldLinear<double, double>& field1_G =
compliant1_G.pressure_field();
const Bvh<Obb, VolumeMesh<double>>& bvh1_G = compliant1_G.bvh();
return ComputeContactSurfaceFromCompliantVolumes(
id0, field0_F, bvh0_F, X_WF, id1, field1_G, bvh1_G, X_WG, representation);
}
/* Calculates the contact surface (if it exists) between two potentially
colliding geometries.
@param object_A_ptr Pointer to the first object in the pair (the order
has no significance).
@param object_B_ptr Pointer to the second object in the pair (the order
has no significance).
@param[out] callback_data Supporting data to compute the contact surface. If
the objects collide, a ContactSurface instance will
be added to the data.
@returns The result from attempting to perform the calculation (indicating if
it was supported or not -- and in what way).
@tparam T The scalar type for the query. */
template <typename T>
CalcContactSurfaceResult MaybeCalcContactSurface(
fcl::CollisionObjectd* object_A_ptr, fcl::CollisionObjectd* object_B_ptr,
CallbackData<T>* data) {
const EncodedData encoding_a(*object_A_ptr);
const EncodedData encoding_b(*object_B_ptr);
// One or two objects have vanished. We can report that we're done
// calculating the contact (no contact).
if (data->geometries.is_vanished(encoding_a.id()) ||
data->geometries.is_vanished(encoding_b.id())) {
return CalcContactSurfaceResult::kCalculated;
}
const HydroelasticType type_A =
data->geometries.hydroelastic_type(encoding_a.id());
const HydroelasticType type_B =
data->geometries.hydroelastic_type(encoding_b.id());
// One or two objects have no hydroelastic type.
if (type_A == HydroelasticType::kUndefined ||
type_B == HydroelasticType::kUndefined) {
return CalcContactSurfaceResult::kUnsupported;
}
// Rigid-rigid contact is not supported in hydroelastic contact model.
// Callers might optionally fall back to point contact model.
if (type_A == HydroelasticType::kRigid &&
type_B == HydroelasticType::kRigid) {
return CalcContactSurfaceResult::kRigidRigid;
}
// Compliant-compliant contact.
if (type_A == HydroelasticType::kSoft && type_B == HydroelasticType::kSoft) {
GeometryId id0 = encoding_a.id();
GeometryId id1 = encoding_b.id();
// Enforce consistent ordering for reproducibility/repeatability of
// simulation since the same pair of geometries (A,B) may be called
// either as (A,B) or (B,A).
if (id0.get_value() > id1.get_value()) {
std::swap(id0, id1);
}
const SoftGeometry& soft0 = data->geometries.soft_geometry(id0);
const SoftGeometry& soft1 = data->geometries.soft_geometry(id1);
// Halfspace vs. halfspace is not supported.
if (soft0.is_half_space() && soft1.is_half_space()) {
return CalcContactSurfaceResult::kHalfSpaceHalfSpace;
}
// Compliant-halfspace vs. compliant-mesh is not supported.
if (soft0.is_half_space() || soft1.is_half_space()) {
return CalcContactSurfaceResult::kCompliantHalfSpaceCompliantMesh;
}
// Compliant mesh vs. compliant mesh.
DRAKE_DEMAND(!soft0.is_half_space() && !soft1.is_half_space());
std::unique_ptr<ContactSurface<T>> surface =
DispatchCompliantCompliantCalculation(soft0, data->X_WGs.at(id0), id0,
soft1, data->X_WGs.at(id1), id1,
data->representation);
if (surface != nullptr) {
DRAKE_DEMAND(surface->id_M() < surface->id_N());
data->surfaces.emplace_back(std::move(*surface));
}
return CalcContactSurfaceResult::kCalculated;
}
// Rigid-compliant contact
DRAKE_DEMAND((type_A == HydroelasticType::kRigid &&
type_B == HydroelasticType::kSoft) ||
(type_A == HydroelasticType::kSoft &&
type_B == HydroelasticType::kRigid));
bool A_is_rigid = type_A == HydroelasticType::kRigid;
const GeometryId id_S = A_is_rigid ? encoding_b.id() : encoding_a.id();
const GeometryId id_R = A_is_rigid ? encoding_a.id() : encoding_b.id();
const SoftGeometry& soft = data->geometries.soft_geometry(id_S);
const RigidGeometry& rigid = data->geometries.rigid_geometry(id_R);
if (soft.is_half_space() && rigid.is_half_space()) {
return CalcContactSurfaceResult::kHalfSpaceHalfSpace;
}
const math::RigidTransform<T>& X_WS(data->X_WGs.at(id_S));
const math::RigidTransform<T>& X_WR(data->X_WGs.at(id_R));
std::unique_ptr<ContactSurface<T>> surface = DispatchRigidSoftCalculation(
soft, X_WS, id_S, rigid, X_WR, id_R, data->representation);
if (surface != nullptr) {
DRAKE_DEMAND(surface->id_M() < surface->id_N());
data->surfaces.emplace_back(std::move(*surface));
}
return CalcContactSurfaceResult::kCalculated;
}
/* Assess contact between two objects -- if it can't be determined with
hydroelastic contact, it throws an exception. All parameters are as documented
in MaybeCalcContactSurface().
@returns `false`; the broad phase should _not_ terminate its process.
@pre `callback_data` must be an instance of CallbackData. */
template <typename T>
bool Callback(fcl::CollisionObjectd* object_A_ptr,
fcl::CollisionObjectd* object_B_ptr,
// NOLINTNEXTLINE
void* callback_data) {
auto& data = *static_cast<CallbackData<T>*>(callback_data);
const EncodedData encoding_a(*object_A_ptr);
const EncodedData encoding_b(*object_B_ptr);
const bool can_collide =
data.collision_filter.CanCollideWith(encoding_a.id(), encoding_b.id());
if (can_collide) {
CalcContactSurfaceResult result =
MaybeCalcContactSurface(object_A_ptr, object_B_ptr, &data);
// Surface calculated; we're done.
if (result == CalcContactSurfaceResult::kCalculated) return false;
const HydroelasticType type_A =
data.geometries.hydroelastic_type(encoding_a.id());
const HydroelasticType type_B =
data.geometries.hydroelastic_type(encoding_b.id());
switch (result) {
case CalcContactSurfaceResult::kUnsupported:
throw std::logic_error(fmt::format(
"Requested a contact surface between a pair of geometries without "
"hydroelastic representation for at least one shape: a {} {} with "
"id {} and a {} {} with id {}",
type_A, GetGeometryName(*object_A_ptr), encoding_a.id(), type_B,
GetGeometryName(*object_B_ptr), encoding_b.id()));
case CalcContactSurfaceResult::kRigidRigid:
throw std::logic_error(fmt::format(
"Requested contact between two rigid objects ({} with id "
"{}, {} with id {}); that is not allowed in hydroelastic-only "
"contact. Please consider using hydroelastics with point-contact "
"fallback, e.g., QueryObject::ComputeContactSurfacesWithFallback() "
"or MultibodyPlant::set_contact_model("
"ContactModel::kHydroelasticWithFallback)",
GetGeometryName(*object_A_ptr), encoding_a.id(),
GetGeometryName(*object_B_ptr), encoding_b.id()));
case CalcContactSurfaceResult::kCompliantHalfSpaceCompliantMesh:
throw std::logic_error(fmt::format(
"Requested hydroelastic contact between two compliant geometries, "
"one of which is a half space ({} with id {}, {} with id {}); "
"that is not allowed",
GetGeometryName(*object_A_ptr), encoding_a.id(),
GetGeometryName(*object_B_ptr), encoding_b.id()));
case CalcContactSurfaceResult::kHalfSpaceHalfSpace:
throw std::logic_error(fmt::format(
"Requested contact between two half spaces with ids {} and {}; "
"that is not allowed",
encoding_a.id(), encoding_b.id()));
case CalcContactSurfaceResult::kCalculated:
// Already handled above.
break;
}
}
// Tell the broadphase to keep searching.
return false;
}
/* Supporting data for the shape-to-shape hydroelastic contact callback with
fallback (see CallbackWithFallback below). It includes:
- CallbackData for strict hydroelastic contact (see above).
- A vector of contacts represented as point pairs -- comprising of those
contacts which could not be evaluated with hydroelastic models.
@tparam T The computation scalar. */
template <typename T>
struct CallbackWithFallbackData {
CallbackData<T> data;
std::vector<PenetrationAsPointPair<T>>* point_pairs;
};
/* Assess contact between two objects -- if it can't be determined with
hydroelastic contact, it assess the contact using point-contact. All parameters
are as documented in MaybeCalcContactSurface(). However, both ContactSurface
and PenetrationAsPointPair instances are written to the `callback_data`.
@returns `false`; the broad phase should _not_ terminate its process.
@pre `callback_data` must be an instance of CallbackWithFallbackData. */
template <typename T>
bool CallbackWithFallback(fcl::CollisionObjectd* object_A_ptr,
fcl::CollisionObjectd* object_B_ptr,
// NOLINTNEXTLINE
void* callback_data) {
auto& data = *static_cast<CallbackWithFallbackData<T>*>(callback_data);
const EncodedData encoding_a(*object_A_ptr);
const EncodedData encoding_b(*object_B_ptr);
const bool can_collide = data.data.collision_filter.CanCollideWith(
encoding_a.id(), encoding_b.id());
if (can_collide) {
CalcContactSurfaceResult result =
MaybeCalcContactSurface(object_A_ptr, object_B_ptr, &data.data);
// Surface calculated; we're done.
if (result == CalcContactSurfaceResult::kCalculated) return false;
// Fall back to point pair.
penetration_as_point_pair::CallbackData<T> point_data{
&data.data.collision_filter, &(data.data.X_WGs), data.point_pairs};
penetration_as_point_pair::Callback<T>(object_A_ptr, object_B_ptr,
&point_data);
}
// Tell the broadphase to keep searching.
return false;
}
// clang-format off
} // namespace hydroelastic
// clang-format on
} // namespace internal
} // namespace geometry
} // namespace drake