-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
polygon_surface_mesh.cc
231 lines (201 loc) · 9.06 KB
/
polygon_surface_mesh.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
#include "drake/geometry/proximity/polygon_surface_mesh.h"
#include <limits>
namespace drake {
namespace geometry {
using math::RigidTransform;
using std::vector;
std::unique_ptr<SurfacePolygon> SurfacePolygon::copy_to_unique() const {
return std::unique_ptr<SurfacePolygon>(
new SurfacePolygon(&mesh_face_data_, index_));
}
template <typename T>
PolygonSurfaceMesh<T>::PolygonSurfaceMesh(vector<int> face_data,
vector<Vector3<T>> vertices)
: face_data_(std::move(face_data)),
vertices_M_(std::move(vertices)),
p_MSc_(Vector3<T>::Zero()) {
ComputePositionDependentQuantities();
}
template <typename T>
void PolygonSurfaceMesh<T>::TransformVertices(const RigidTransform<T>& X_NM) {
for (auto& v : vertices_M_) {
v = X_NM * v;
}
for (auto& n : face_normals_) {
n = (X_NM.rotation() * n).normalized();
}
for (auto& c : element_centroid_M_) {
c = X_NM * c;
}
p_MSc_ = X_NM * p_MSc_;
}
template <typename T>
std::pair<Vector3<T>, Vector3<T>> PolygonSurfaceMesh<T>::CalcBoundingBox()
const {
Vector3<T> min_extent =
Vector3<T>::Constant(std::numeric_limits<double>::max());
Vector3<T> max_extent =
Vector3<T>::Constant(std::numeric_limits<double>::lowest());
for (const auto& vertex : vertices_M_) {
min_extent = min_extent.cwiseMin(vertex);
max_extent = max_extent.cwiseMax(vertex);
}
Vector3<T> center = (max_extent + min_extent) / 2.0;
Vector3<T> size = max_extent - min_extent;
return std::make_pair(center, size);
}
template <typename T>
bool PolygonSurfaceMesh<T>::Equal(const PolygonSurfaceMesh<T>& mesh) const {
if (this == &mesh) return true;
if (this->num_faces() != mesh.num_faces()) return false;
if (this->num_vertices() != mesh.num_vertices()) return false;
if (this->vertices_M_ != mesh.vertices_M_) return false;
if (this->poly_indices_ != mesh.poly_indices_) return false;
if (this->face_data_ != mesh.face_data_) return false;
return true;
}
template <class T>
void PolygonSurfaceMesh<T>::ComputePositionDependentQuantities() {
/* Reset all areas, normals, and centroids because we accumulate into them. */
total_area_ = 0;
areas_.clear();
face_normals_.clear();
poly_indices_.clear();
p_MSc_.setZero();
element_centroid_M_.clear();
/* Build the polygons and derived quantities from the given data. */
int poly_count = -1;
int i = 0;
while (i < static_cast<int>(face_data_.size())) {
poly_indices_.push_back(i);
CalcAreaNormalAndCentroid(++poly_count);
i += face_data_[i] + 1; /* Jump to the next polygon. */
}
DRAKE_DEMAND(poly_indices_.size() == areas_.size());
DRAKE_DEMAND(poly_indices_.size() == face_normals_.size());
}
template <class T>
void PolygonSurfaceMesh<T>::CalcAreaNormalAndCentroid(const int poly_index) {
const int data_index = poly_indices_[poly_index];
const int v_count = face_data_[data_index];
const int v_index_offset = data_index + 1;
const Vector3<T>& r_MA = vertices_M_[face_data_[v_index_offset]];
Vector3<T> p_MTc_scaled(0, 0, 0);
Vector3<T> normal_M{0, 0, 0};
Vector3<T> cross;
T cross_magnitude{};
T double_poly_area(0);
// TODO(SeanCurtis-TRI): We could reduce square roots by computing the normal
// from the first triangle and use that to facilitate area calculations for
// subsequent triangles. This *kind* of would allow this algorithm to work
// with non-convex polygons. However, where it fails is if the first triangle
// has locally reversed winding. The normal direction would not be as
// expected. The best solution is to pass in normals upon construction (we
// have them in computing a contact surface) and use those normals to compute
// area.
/* Compute the polygon *total* area and centroid (Tc) by decomposing it into a
set of triangles. The triangles are defined as a triangle fan around vertex
0. We use the normal of the last triangle in the fan as the face normal; this
only works because of the requirement that the polygons be strictly planar
and convex.
For efficiency, we actually accumulate 2 * area in double_face_area and
6 * p_MTc in p_MTc_scaled to defer otherwise redundant scaling operations. */
for (int v = 1; v < v_count - 1; ++v) {
const Vector3<T>& r_MB = vertices_M_[face_data_[v_index_offset + v]];
const Vector3<T>& r_MC = vertices_M_[face_data_[v_index_offset + v + 1]];
const auto r_UV_M = r_MB - r_MA;
const auto r_UW_M = r_MC - r_MA;
cross = r_UV_M.cross(r_UW_M);
normal_M += cross;
/* The cross product magnitude is equal to twice the triangle area. */
cross_magnitude = cross.norm();
double_poly_area += cross_magnitude;
/* The triangle centroid Tc is the mean position of the vertices:
(A + B + C) / 3. Its contribution to the polygon centroid is scaled by
its portion of the polygon area: Aₜ⋅(A + B + C) / (3⋅Aₚ) (where Aₜ is the
triangle area and Aₚ is the polygon area). Because we are doing this
weighted sum, it doesn't matter if the scale factor is Aₜ / (3⋅Aₚ) or
k⋅Aₜ / (k⋅3⋅Aₚ), the scale factors cancel out. So, that means we can ignore
the factor of two in the "double" area and omit the divisor 3 in the mean
vertex position when combining triangle centroids; *proportional*
contribution remains the same. We'll deal with this factor 6 when we need
the final value.
The area value used here is an *absolute* area value. This requires the
polygon to be convex (although it does allow for collinear vertices). To
allow for non-convex polygons, we'd have to have a *signed* area. This is
most easily done by using a known normal direction for computing the
area. */
p_MTc_scaled += cross_magnitude * (r_MA + r_MB + r_MC);
}
/* Correct the scaled poly centroid as documented above. */
const T poly_area = double_poly_area / 2;
areas_.push_back(poly_area);
/* We've accumulated all of the cross products into normal_M. The resulting
vector result is the area-scaled polygon normal. This vector sum protects us
from any triangles in the fan that may have zero area, or even if the
triangle is non-convex. However, this robust *normal* calculation still
does not allow this function to support non-convex polygons (see the note
on computing the polygon centroid). If the area of the polygon is properly
zero, we rely on Eigen's documented behavior that the normalized zero vector
is the zero vector.
https://eigen.tuxfamily.org/dox/classEigen_1_1MatrixBase.html#a5cf2fd4c57e59604fd4116158fd34308
*/
face_normals_.emplace_back(normal_M.normalized());
/* Accumulate the contribution of *this* polygon into the mesh centroid. We
implicitly have the weighted contribution of all previous polygons as the
product of current centroid and total area. This allows us to add in this
polygon's contribution proportionately (although, we want to add in Aₚ⋅p_MFc,
but what we computed is 6⋅Aₚ⋅p_MFc. */
const Vector3<T> p_MTc_area_scaled = p_MTc_scaled / 6;
if (poly_area != 0.0) {
element_centroid_M_.emplace_back(p_MTc_area_scaled / poly_area);
} else {
// For a zero-area polygon, we will use the average vertex position.
// It may or may not be in the "middle" of the zero-area polygon.
element_centroid_M_.emplace_back(CalcAveragePosition(poly_index));
}
const T old_area = total_area_;
total_area_ += poly_area;
if (total_area_ != 0.0) {
p_MSc_ = (p_MSc_ * old_area + p_MTc_area_scaled) / total_area_;
} else {
// The accumulated total area is still zero, so we simply use the last
// entry of element_centroid_M_ as a temporary solution for now. If the
// next polygon in the mesh has non-zero area, its centroid will take over.
p_MSc_ = element_centroid_M_.back();
}
}
template <class T>
Vector3<T> PolygonSurfaceMesh<T>::CalcAveragePosition(const int poly_index) {
const int data_index = poly_indices_[poly_index];
const int v_count = face_data_[data_index];
const int v_index_offset = data_index + 1;
const Vector3<T>& r_MA = vertices_M_[face_data_[v_index_offset]];
// Initialize the accumulation from the first vertex.
Vector3<T> p_MVaccumulate(r_MA);
// Accumulate the remaining vertices.
for (int v = 1; v < v_count; ++v) {
p_MVaccumulate += vertices_M_[face_data_[v_index_offset + v]];
}
return p_MVaccumulate / v_count;
}
template <class T>
void PolygonSurfaceMesh<T>::SetAllPositions(
const Eigen::Ref<const VectorX<T>>& p_MVs) {
if (p_MVs.size() != 3 * num_vertices()) {
throw std::runtime_error(
fmt::format("SetAllPositions(): Attempting to deform a mesh with {} "
"vertices with data for {} DoFs",
num_vertices(), p_MVs.size()));
}
for (int v = 0, i = 0; v < num_vertices(); ++v, i += 3) {
vertices_M_[v] = Vector3<T>(p_MVs[i], p_MVs[i + 1], p_MVs[i + 2]);
}
// Update position dependent quantities after the vertex positions have been
// updated.
ComputePositionDependentQuantities();
}
DRAKE_DEFINE_CLASS_TEMPLATE_INSTANTIATIONS_ON_DEFAULT_NONSYMBOLIC_SCALARS(
class PolygonSurfaceMesh)
} // namespace geometry
} // namespace drake