-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
mesh_intersection.h
456 lines (416 loc) · 21.2 KB
/
mesh_intersection.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
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
#pragma once
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>
#include "drake/common/eigen_types.h"
#include "drake/geometry/geometry_ids.h"
#include "drake/geometry/proximity/bvh.h"
#include "drake/geometry/proximity/contact_surface_utility.h"
#include "drake/geometry/proximity/polygon_surface_mesh.h"
#include "drake/geometry/proximity/polygon_surface_mesh_field.h"
#include "drake/geometry/proximity/posed_half_space.h"
#include "drake/geometry/proximity/triangle_surface_mesh.h"
#include "drake/geometry/proximity/triangle_surface_mesh_field.h"
#include "drake/geometry/proximity/volume_mesh.h"
#include "drake/geometry/proximity/volume_mesh_field.h"
#include "drake/geometry/query_results/contact_surface.h"
#include "drake/math/rigid_transform.h"
namespace drake {
namespace geometry {
namespace internal {
/* Calculates the intersection point between an infinite straight line
spanning points A and B and the bounding plane of the half space H.
@param p_FA
Point A measured and expressed in the common frame F.
@param p_FB
Point B measured and expressed in the common frame F.
@param H_F
The half space H measured and expressed in frame F (i.e., points also
measured and expressed in frame F can be tested against it).
@pre
A and B are two different points, and their spanning line is not
parallel to the bounding plane of the half space H.
*/
template <typename T>
Vector3<T> CalcIntersection(const Vector3<T>& p_FA, const Vector3<T>& p_FB,
const PosedHalfSpace<T>& H_F);
/* Intersects a polygon with the half space H. It keeps the part of
the polygon contained in the half space (signed distance is <= 0).
The half space `H_F` and vertex positions of `input_vertices_F` are both
defined in a common frame F.
@param[in] input_vertices_F
Input polygon is represented as a sequence of positions of its vertices.
The input polygon is allowed to have zero area.
@param[in] H_F
The clipping half space H in frame F.
@param[out] output_vertices_F
Output polygon is represented as a sequence of positions of its vertices.
It could be an empty sequence if the input polygon is entirely outside
the half space. It could be the same as the input polygon if the input
polygon is entirely inside the half space. The output polygon is
guaranteed to be planar (within floating point tolerance) and, if the
polygon has area, the normal implied by the winding will be the same
as the input polygon.
@pre `input_vertices_F` has at least three vertices.
@pre the vertices in `input_vertices_F` are all planar.
@note
1. For an input polygon P that is parallel to the plane of the half
space, there are three cases:
1.1 If P is completely inside the half space, the output polygon
will be the same as P.
1.2 If P is completely outside the half space, the output polygon
will be empty.
1.3 If P is on the plane of the half space, the output polygon will
be the same as P.
2. For an input polygon P outside the half space with one edge on the
plane of the half space, the output polygon will be a zero-area
4-gon with two pairs of duplicate vertices.
3. For an input polygon P outside the half space with one vertex on the
plane of the half space, the output polygon will be a zero-area
triangle with three duplicate vertices.
*/
template <typename T>
void ClipPolygonByHalfSpace(const std::vector<Vector3<T>>& input_vertices_F,
const PosedHalfSpace<T>& H_F,
std::vector<Vector3<T>>* output_vertices_F);
/* Removes nearly duplicate vertices from a polygon represented as a cyclical
sequence of vertex positions. We consider two positions nearly duplicate when
they are within 1e-14 meter ε tolerance. For example, with input
`A,B,B+ε,C,A+ε`, the result would be `A,B,C`. The polygon might be reduced
to a pair of points (i.e., `A,A+ε,B,B+ε` becomes `A,B`) or a single point
(i.e., `A,A+ε,A` becomes `A`).
@param[in,out] polygon
The input polygon, and the output equivalent polygon with no duplicate
vertices.
*/
template <typename T>
void RemoveNearlyDuplicateVertices(std::vector<Vector3<T>>* polygon);
// Forward declaration of Tester class, so we can grant friend access.
template <typename MeshBuilder> class SurfaceVolumeIntersectorTester;
/* %SurfaceVolumeIntersector performs a mesh-intersection algorithm between a
triangulated surface mesh and a tetrahedral volume mesh with a field
variable. It also interpolates the field variable onto the resulting
surface.
@tparam MeshBuilder The type of mesh-output builder of the contact
surface. It can be TriMeshBuilder<T> or PolyMeshBuilder<T> for T = double
or AutoDiffXd.
@tparam BvType The type of bounding volumes for the tetrahedra in the
volume mesh. It can be Obb for hydroelastics or Aabb for deformables.
*/
template <typename MeshBuilder, typename BvType>
class SurfaceVolumeIntersector {
public:
using MeshType = typename MeshBuilder::MeshType;
using T = typename MeshType::ScalarType;
using FieldType = typename MeshBuilder::FieldType;
SurfaceVolumeIntersector() {
// We know that each contact polygon has at most 7 vertices.
// Each surface triangle is clipped by four half-spaces of the four
// triangular faces of a tetrahedron.
polygon_[0].reserve(7);
polygon_[1].reserve(7);
}
virtual ~SurfaceVolumeIntersector();
// TODO(DamrongGuoy): Maintain book keeping to avoid duplicate vertices and
// remove the note in the function documentation.
/* Samples a field on a two-dimensional manifold. The field is defined over
a volume mesh and the manifold is the intersection of the volume mesh and a
surface mesh. The resulting manifold's topology is a function of both the
volume and surface mesh topologies. The winding of the resulting manifold's
faces will be such that its face normals will point in the same direction as
the input surface mesh's corresponding faces.
@param[in] volume_field_M
The field to sample from. The field contains the volume mesh M that
defines its domain. The vertex positions of the mesh are measured and
expressed in frame M. And the field can be evaluated at positions
likewise measured and expressed in frame M.
@param[in] bvh_M
A bounding volume hierarchy built on the geometry contained in
`volume_field_M`.
@param[in] surface_N
The surface mesh intersected with the volume mesh to define the sample
domain. Its vertex positions are measured and expressed in frame N.
@param[in] bvh_N
A bounding volume hierarchy built on the geometry `surface_N`.
@param[in] X_MN
The pose of frame N in frame M.
@param[in] filter_face_normal_along_field_gradient
If true, allow only contact polygons whose face normals are "along"
the direction of field gradient vectors. See
IsFaceNormalAlongPressureGradient().
@note
The output surface mesh (see mutable_mesh() and release_mesh()) may
have duplicate vertices.
*/
void SampleVolumeFieldOnSurface(
const VolumeMeshFieldLinear<double, double>& volume_field_M,
const Bvh<BvType, VolumeMesh<double>>& bvh_M,
const TriangleSurfaceMesh<double>& surface_N,
const Bvh<Obb, TriangleSurfaceMesh<double>>& bvh_N,
const math::RigidTransform<T>& X_MN,
bool filter_face_normal_along_field_gradient = true);
bool has_intersection() const { return mesh_M_ != nullptr; }
/* Returns surface_MN_M the intersection surface between the volume mesh M
and the surface N. Vertex positions are measured and expressed in M's frame.
@pre has_intersection() is true. */
MeshType& mutable_mesh() { return *mesh_M_; }
/* Releases the ownership of surface_MN_M described in mutable_mesh(). */
std::unique_ptr<MeshType>&& release_mesh() { return std::move(mesh_M_); }
/* Returns e_MN the sampled field values on the intersecting surface
(samples to support a linear mesh field -- i.e., one per vertex).
@pre has_intersection() is true. */
FieldType& mutable_field() { return *field_M_; }
/* Releases the ownership of e_MN described in mutable_field() */
std::unique_ptr<FieldType>&& release_field() { return std::move(field_M_); }
/* Returns grad_eM_Ms the gradient of the volumetric mesh field on
the contact polygons (one entry per polygon in `surface_MN_M`). */
std::vector<Vector3<T>>& mutable_grad_eM_M() { return grad_eM_Ms_; }
protected:
/* Internal function to process a possible contact between a single
tetrahedron in volume_field_M and a single triangle in surface_N.
@param[in, out] builder_M
If the tetrahedron and the triangle have a non-empty intersection, the
intersecting polygon (contact polygon) and its field values are added
into builder_M. The added polygon is in the M frame.
@param[in] filter_face_normal_along_field_gradient
If true, allow only contact polygons whose face normals are "along"
the direction of field gradient vectors. If false, do not perform this
filtering. See IsFaceNormalAlongPressureGradient().
@param[in] tet_index
The index of the tetrahedron in the mesh of volume_field_M considered
for intersection with the triangle.
@param[in] tri_index
The index of the triangle in the surface mesh surface_N considered for
intersection with the tetrahedron.
@note Subclasses may override this function to collect more data for the
contact. In that case, they must invoke the base class implementation.
An alternative design would be to make this a NVI so that subclasses
don't have to remember to call the base implementation. That, however, is
awkward because the base class has to work against "arbitrary"
MeshBuilder types (its template parameter) and it has to provide the data
required by the additional work performed by the subclasses' extention.
On the other hand, derived classes (e.g.
DeformableSurfaceVolumeIntersector) are often no longer templated on the
MeshBuilder type so they can take shortcuts based on a fixed type that
greatly simplifies things. So, while NVI is possible, it would end up
doing a fair amount of thrashing of code (including changing the APIs of
the MeshBuilder types as well). It doesn't seem like a clear win. */
virtual void CalcContactPolygon(
const VolumeMeshFieldLinear<double, double>& volume_field_M,
const TriangleSurfaceMesh<double>& surface_N,
const math::RigidTransform<T>& X_MN,
const math::RigidTransform<double>& X_MN_d,
MeshBuilder* builder_M,
bool filter_face_normal_along_field_gradient,
int tet_index, int tri_index);
private:
/* Intersects a triangle with a tetrahedron, returning the portion of the
triangle with non-zero area contained in the tetrahedron.
@param element
Index of the tetrahedron in a volume mesh.
@param volume_M
The volume mesh whose vertex positions are expressed in M's frame.
@param face
Index of the triangle in a surface mesh.
@param surface_N
The surface mesh whose vertex positions are expressed in N's frame.
@param X_MN
The pose of the surface frame N in the volume frame M.
@retval polygon_M
The output polygon represented by a sequence of positions of its
vertices, expressed in M's frame. The nature of triangle-tetrahedron
intersection means that this polygon can have up to seven vertices.
The following picture shows such an example. The plane of the triangle
cuts the tetrahedron into a rectangle ABCD with A lying inside the
triangle. Each of B,C,D is outside the triangle and has two edges that
intersect an edge of the triangle.
*
* *
* *
* *
* *
* A------x-----D
* | * |
*| x
x | *
|* x
| * * |
B--x---x-----C
*
@note
1. If the triangle is outside the tetrahedron with one vertex on a
face of the tetrahedron, the output polygon will be empty.
2. If the triangle is outside the tetrahedron with an edge on a face
of the tetrahedron, the output polygon will be empty.
3. If the triangle lies on the plane of a tetrahedron face, the output
polygon will be that part of the triangle inside the face of the
tetrahedron (non-zero area restriction still applies).
*/
const std::vector<Vector3<T>>& ClipTriangleByTetrahedron(
int element, const VolumeMesh<double>& volume_M, int face,
const TriangleSurfaceMesh<double>& surface_N,
const math::RigidTransform<T>& X_MN);
/* Determines whether a triangle of a rigid surface N and a tetrahedron of a
soft volume M are suitable for building contact surface based on the face
normal vector f_N of the triangle and the pressure gradient vector ∇p_M
of the tetrahedron. This is an attempt to address Issue #12441 "Hydroelastic
contact surface broken for thin rigid object -- needs to use normals".
For example, when a thin rigid plate N penetrates deeply into a soft
ball M, both sides of surface N intersect the volume of M as shown in this
picture:
thin rigid plate N
┌┄┐
┊ ┊ soft ball M
┊ ┊ ● ● ● ●
┊ ║● ●
⇦┃↘║⇨ ●
●⇦┃↘║↘ ●
● ⇦┃ ║⇨ ↘ ●
● ⇦┃ ║⇨ ↘ ●
● ⇦┃→║⇨ → → → ●
● ⇦┃ ║⇨ ↗ ● ↗ pressure gradient ∇p_M in M
● ⇦┃ ║⇨ ↗ ● ⇨ surface normal f_N on N
●⇦┃↗║↗ ● ║ suitable intersecting surface
⇦┃↗║⇨ ● ┃ unsuitable intersecting surface
┊ ║● ● ┊ non-intersecting surface
┊ ┊ ● ● ● ●
┊ ┊
└┄┘
In the picture above, each suitable triangle in N has its face normal making
an acute angle with the pressure gradient in M, and each unsuitable triangle
has its face normal vector making an obtuse angle with the pressure gradient.
In this case, we can use π/2 as the angle threshold to distinguish the two
kinds of triangles in N.
However, there is no single angle threshold that works for all cases.
For example, a rigid box N penetrates into a soft ball M (see the
following picture) and has triangles on its left side and right side with
face normals that make obtuse angles with the pressure gradient. Using
π/2 as the threshold, we would prohibit these triangles from the contact
surface.
soft ball M
● ● ● ●
● ●
● ●
● ●
● ●
● ●
● ●
● ↗ ↑ ↖ ● ↗ pressure gradient ∇p_M in M
● ↗ ⇧ ↑ ⇧ ↖ ● ⇧ face normal f_N of N
● ╔═══════╗ ● ║ suitable intersecting surface
● ⇦┃↗ ↖┃⇨ ● ┃ incorrectly prohibited intersecting
● ⇦┃↗ ↖┃⇨ ● surface with π/2 threshold
┃● ● ● ●┃ ┊ non-intersecting surface
┊ ┊
┊ ┊
┊ ┊
└┄┄┄┄┄┄┄┘
rigid box N
@param[in] volume_field_M
The pressure field defined on the volume mesh M. Its gradient vectors are
expressed in frame M.
@param[in] surface_N
Surface mesh N of the rigid geometry. Its face normal vectors are
expressed in frame N.
@param[in] X_MN
Pose of frame N in frame M.
@param[in] tet_index
Index of the tetrahedron in the volume mesh M.
@param[in] tri_index
Index of the triangle in the surface mesh N.
@return true if the two vectors make an angle less than an internal
threshold.
@note This function is a work in progress. There is no single threshold
that works for all cases. We pick 5π/8 empirically.
See @ref module_contact_surface.
*/
static bool IsFaceNormalAlongPressureGradient(
const VolumeMeshFieldLinear<double, double>& volume_field_M,
const TriangleSurfaceMesh<double>& surface_N,
const math::RigidTransform<double>& X_MN, int tet_index, int tri_index);
// To avoid heap allocation by std::vector in low-level functions, we use
// these member variables instead of local variables in the functions.
// The two vectors are not guaranteed to have any particular semantic
// interpretation during the execution of this class's main method. This array
// represents a pool of resources; any entry could have arbitrary meaning (or
// none at all) depending where in the algorithm they are inspected.
// Furthermore, any changes to the existing algorithm that make use of these
// pool variables should take care that conflicting use of the resources are
// not introduced.
std::vector<Vector3<T>> polygon_[2];
// A container for the vertex indices that define an intersection polygon.
// By making it a member, we only allocate on the heap *once* for a pair
// of meshes (instead of once per intersecting polygon). This exists purely
// for performance optimization.
std::vector<int> polygon_vertex_indices_;
// The mesh produced from intersecting the volume mesh with the surface mesh,
// measured and expressed in the volume mesh's frame M.
std::unique_ptr<MeshType> mesh_M_;
// The field defined on mesh_M_ (in the same frame M).
std::unique_ptr<FieldType> field_M_;
// The spatial gradient of the volume mesh field, sampled once for each face
// in mesh_M_.
std::vector<Vector3<T>> grad_eM_Ms_;
friend class SurfaceVolumeIntersectorTester<MeshBuilder>;
};
/* Computes the contact surface between a soft geometry S and a rigid
geometry R.
@param[in] id_S
Id of the soft geometry S.
@param[in] field_S
A scalar field defined on the soft volume mesh S. Mesh S's vertices are
defined in S's frame. The scalar field is likewise defined in frame S
(that is, it can only be evaluated on points which have been measured and
expressed in frame S). For hydroelastic contact, the scalar field is a
"pressure" field.
@param[in] bvh_S
A bounding volume hierarchy built on the geometry contained in `field_S`.
@param[in] X_WS
The pose of the rigid frame S in the world frame W.
@param[in] id_R
Id of the rigid geometry R.
@param[in] mesh_R
The rigid geometry R is represented as a surface mesh, whose vertex
positions are in R's frame. We assume that triangles are oriented
outward.
@param[in] bvh_R
A bounding volume hierarchy built on the geometry contained in `mesh_R`.
@param[in] X_WR
The pose of the rigid frame R in the world frame W.
@param[in] representation
The preferred representation of each contact polygon.
@return
The contact surface between M and N. Geometries S and R map to M and N
with a consistent mapping (as documented in ContactSurface) but without any
guarantee as to what that mapping is. Positions of vertex coordinates are
expressed in the world frame. The pressure distribution comes from the
soft geometry S.
ooo soft S
o o
o o = Contact surface (M(S, R), N(S, R)).
o ↑↑↑↑↑↑↑ o ↑ Vector field from R to S is upwards.
+------=========-------+
| o o |
rigid R | ooo |
| |
+----------------------+
If there is no contact, nullptr is returned.
@tparam_nonsymbolic_scalar
@note This definition of the function assumes that the meshes involved are
*double* valued -- in other words, they are constant parameters in the
calculation. If derivatives are to be found, the point of injection is through
the definition of the relative position of the two meshes. */
template <typename T>
std::unique_ptr<ContactSurface<T>>
ComputeContactSurfaceFromSoftVolumeRigidSurface(
const GeometryId id_S, const VolumeMeshFieldLinear<double, double>& field_S,
const Bvh<Obb, VolumeMesh<double>>& bvh_S,
const math::RigidTransform<T>& X_WS,
const GeometryId id_R, const TriangleSurfaceMesh<double>& mesh_R,
const Bvh<Obb, TriangleSurfaceMesh<double>>& bvh_R,
const math::RigidTransform<T>& X_WR,
HydroelasticContactRepresentation representation);
} // namespace internal
} // namespace geometry
} // namespace drake