Skip to content

Commit 1339890

Browse files
committed
Fix dihedral angle calculation; add normal hessians
1 parent 296fe4a commit 1339890

File tree

6 files changed

+327
-39
lines changed

6 files changed

+327
-39
lines changed

python/src/geometry/normal.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,23 @@ void define_normal(py::module_& m)
230230
)ipc_qu8mg5v7",
231231
py::arg("a"), py::arg("b"), py::arg("c"));
232232

233+
m.def(
234+
"triangle_normal_hessian", &triangle_normal_hessian,
235+
R"ipc_qu8mg5v7(
236+
Computes the Hessian of the normal vector of a triangle.
237+
238+
Parameters
239+
----------
240+
a: The first vertex of the triangle.
241+
b: The second vertex of the triangle.
242+
c: The third vertex of the triangle.
243+
244+
Returns
245+
-------
246+
The Hessian of the normal vector of the triangle.
247+
)ipc_qu8mg5v7",
248+
py::arg("a"), py::arg("b"), py::arg("c"));
249+
233250
m.def(
234251
"edge_edge_unnormalized_normal", &edge_edge_unnormalized_normal,
235252
R"ipc_qu8mg5v7(
@@ -302,4 +319,41 @@ void define_normal(py::module_& m)
302319
The Jacobian of the normal vector of the two edges.
303320
)ipc_qu8mg5v7",
304321
py::arg("ea0"), py::arg("ea1"), py::arg("eb0"), py::arg("eb1"));
322+
323+
m.def(
324+
"edge_edge_unnormalized_normal_hessian",
325+
&edge_edge_unnormalized_normal_hessian,
326+
R"ipc_qu8mg5v7(
327+
Computes the Hessian of the unnormalized normal vector of two edges.
328+
329+
Parameters
330+
----------
331+
ea0: The first vertex of the first edge.
332+
ea1: The second vertex of the first edge.
333+
eb0: The first vertex of the second edge.
334+
eb1: The second vertex of the second edge.
335+
336+
Returns
337+
-------
338+
The Hessian of the unnormalized normal vector of the two edges.
339+
)ipc_qu8mg5v7",
340+
py::arg("ea0"), py::arg("ea1"), py::arg("eb0"), py::arg("eb1"));
341+
342+
m.def(
343+
"edge_edge_normal_hessian", &edge_edge_normal_hessian,
344+
R"ipc_qu8mg5v7(
345+
Computes the Hessian of the normal vector of two edges.
346+
347+
Parameters
348+
----------
349+
ea0: The first vertex of the first edge.
350+
ea1: The second vertex of the first edge.
351+
eb0: The first vertex of the second edge.
352+
eb1: The second vertex of the second edge.
353+
354+
Returns
355+
-------
356+
The Hessian of the normal vector of the two edges.
357+
)ipc_qu8mg5v7",
358+
py::arg("ea0"), py::arg("ea1"), py::arg("eb0"), py::arg("eb1"));
305359
}

src/ipc/geometry/angle.cpp

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,14 @@ double dihedral_angle(
1010
Eigen::ConstRef<Eigen::Vector3d> x2,
1111
Eigen::ConstRef<Eigen::Vector3d> x3)
1212
{
13-
double cos_theta =
14-
triangle_normal(x0, x1, x2).dot(triangle_normal(x1, x0, x3));
15-
assert(std::abs(cos_theta) <= 1.0 + 1e-6); // Numerical tolerance
16-
return std::acos(std::clamp(cos_theta, -1.0, 1.0));
13+
const Eigen::Vector3d n0 = triangle_normal(x0, x1, x2);
14+
const Eigen::Vector3d n1 = triangle_normal(x1, x0, x3);
15+
const Eigen::Vector3d e = (x1 - x0).normalized();
16+
17+
const double sin_theta = n0.cross(n1).dot(e);
18+
const double cos_theta = n0.dot(n1);
19+
20+
return std::atan2(sin_theta, cos_theta);
1721
}
1822

1923
Eigen::Vector<double, 12> dihedral_angle_gradient(
@@ -24,33 +28,42 @@ Eigen::Vector<double, 12> dihedral_angle_gradient(
2428
{
2529
const Eigen::Vector3d n0 = triangle_normal(x0, x1, x2);
2630
const Eigen::Vector3d n1 = triangle_normal(x1, x0, x3);
31+
const Eigen::Vector3d e = (x1 - x0).normalized();
32+
33+
// --- Normal gradients ---
34+
35+
Eigen::Matrix<double, 3, 12> dn0_dx;
36+
dn0_dx.leftCols<9>() = triangle_normal_jacobian(x0, x1, x2);
37+
dn0_dx.rightCols<3>().setZero();
38+
39+
Eigen::Matrix<double, 3, 12> dn1_dx;
40+
const std::array<int, 9> idx = { { 3, 4, 5, 0, 1, 2, 9, 10, 11 } };
41+
dn1_dx(Eigen::all, idx) = triangle_normal_jacobian(x1, x0, x3);
42+
dn1_dx.middleCols<3>(6).setZero();
43+
44+
// --- Edge gradient ---
45+
46+
Eigen::Matrix<double, 3, 12> de_dx;
47+
de_dx.middleCols<3>(3) = normalization_jacobian(x1 - x0);
48+
de_dx.leftCols<3>() = -de_dx.middleCols<3>(3);
49+
de_dx.rightCols<6>().setZero();
50+
51+
// --- Angle gradient ---
52+
53+
const Eigen::Vector<double, 12> dcos_dx =
54+
dn0_dx.transpose() * n1 + dn1_dx.transpose() * n0;
55+
56+
const Eigen::Vector<double, 12> dsin_dx =
57+
(cross_product_matrix(n0) * dn1_dx - cross_product_matrix(n1) * dn0_dx)
58+
.transpose()
59+
* e;
60+
61+
// --- Product rule ---
2762

28-
const Eigen::Matrix<double, 3, 9> dn0_dx =
29-
triangle_normal_jacobian(x0, x1, x2);
30-
const Eigen::Matrix<double, 3, 9> dn1_dx =
31-
triangle_normal_jacobian(x1, x0, x3);
32-
33-
const Eigen::Matrix3d dn0_dx0 = dn0_dx.block<3, 3>(0, 0);
34-
const Eigen::Matrix3d dn0_dx1 = dn0_dx.block<3, 3>(0, 3);
35-
const Eigen::Matrix3d dn0_dx2 = dn0_dx.block<3, 3>(0, 6);
36-
const Eigen::Matrix3d dn1_dx0 = dn1_dx.block<3, 3>(0, 3);
37-
const Eigen::Matrix3d dn1_dx1 = dn1_dx.block<3, 3>(0, 0);
38-
const Eigen::Matrix3d dn1_dx3 = dn1_dx.block<3, 3>(0, 6);
39-
40-
Eigen::Vector<double, 12> gradient;
41-
// Product rule
42-
gradient.segment<3>(0) =
43-
dn0_dx0.transpose() * n1 + dn1_dx0.transpose() * n0;
44-
gradient.segment<3>(3) =
45-
dn0_dx1.transpose() * n1 + dn1_dx1.transpose() * n0;
46-
gradient.segment<3>(6) = dn0_dx2.transpose() * n1;
47-
gradient.segment<3>(9) = dn1_dx3.transpose() * n0;
48-
49-
// Chain rule through acos
63+
const double sin_theta = n0.cross(n1).dot(e);
5064
const double cos_theta = n0.dot(n1);
51-
gradient *= -1 / sqrt(1.0 - cos_theta * cos_theta);
5265

53-
return gradient;
66+
return dsin_dx * cos_theta - dcos_dx * sin_theta;
5467
}
5568

5669
} // namespace ipc

src/ipc/geometry/normal.cpp

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,4 +127,102 @@ Eigen::Matrix<double, 3, 81> triangle_unnormalized_normal_hessian(
127127
return H.reshaped(3, 81);
128128
}
129129

130+
Eigen::Matrix<double, 3, 81> triangle_normal_hessian(
131+
Eigen::ConstRef<Eigen::Vector3d> a,
132+
Eigen::ConstRef<Eigen::Vector3d> b,
133+
Eigen::ConstRef<Eigen::Vector3d> c)
134+
{
135+
const Eigen::Vector3d z = triangle_unnormalized_normal(a, b, c);
136+
const double z_norm2 = z.squaredNorm();
137+
const double z_norm = std::sqrt(z_norm2);
138+
const double z_norm3 = z_norm2 * z_norm;
139+
140+
const auto dz_dx = triangle_unnormalized_normal_jacobian(a, b, c);
141+
const auto d2z_dx2 = triangle_unnormalized_normal_hessian(a, b, c);
142+
143+
Eigen::Matrix<double, 3, 81> d2n_dx2;
144+
for (int k = 0; k < 81; ++k) {
145+
const int i = k / 9, j = k % 9;
146+
147+
const double alpha = z.dot(dz_dx.col(i)) / z_norm3;
148+
149+
const double dalpha_dxj =
150+
(dz_dx.col(j).dot(dz_dx.col(i)) + z.dot(d2z_dx2.col(k))
151+
- 3 * z.dot(dz_dx.col(i)) * dz_dx.col(j).dot(z) / z_norm2)
152+
/ z_norm3;
153+
154+
d2n_dx2.col(k) = -dz_dx.col(i) * z.transpose() * dz_dx.col(j) / z_norm3
155+
+ d2z_dx2.col(k) / z_norm - alpha * dz_dx.col(j) - dalpha_dxj * z;
156+
}
157+
158+
return d2n_dx2;
159+
}
160+
161+
// --- edge-edge normal functions ---------------------------------------------
162+
163+
Eigen::Matrix<double, 3, 144> edge_edge_unnormalized_normal_hessian(
164+
Eigen::ConstRef<Eigen::Vector3d> ea0,
165+
Eigen::ConstRef<Eigen::Vector3d> ea1,
166+
Eigen::ConstRef<Eigen::Vector3d> eb0,
167+
Eigen::ConstRef<Eigen::Vector3d> eb1)
168+
{
169+
Eigen::Matrix<double, 36, 12> H = Eigen::Matrix<double, 36, 12>::Zero();
170+
171+
// ∂²n/∂a² = 0
172+
// ∂²n/∂a∂b = 0
173+
set_cross_product_matrix_jacobian(H.block<9, 3>(0, 6), -1.0); // ∂²n/∂a∂c
174+
set_cross_product_matrix_jacobian(H.block<9, 3>(0, 9), 1.0); // ∂²n/∂a∂d
175+
/**/
176+
// ∂²n/∂b∂a = 0
177+
// ∂²n/∂b² = 0
178+
set_cross_product_matrix_jacobian(H.block<9, 3>(9, 6), 1.0); // ∂²n/∂b∂c
179+
set_cross_product_matrix_jacobian(H.block<9, 3>(9, 9), -1.0); // ∂²n/∂b∂d
180+
/**/
181+
set_cross_product_matrix_jacobian(H.block<9, 3>(18, 0), 1.0); // ∂²n/∂c∂a
182+
set_cross_product_matrix_jacobian(H.block<9, 3>(18, 3), -1.0); // ∂²n/∂c∂b
183+
// ∂²n/∂c² = 0
184+
// ∂²n/∂d² = 0
185+
/**/
186+
set_cross_product_matrix_jacobian(H.block<9, 3>(27, 0), -1.0); // ∂²n/∂d∂a
187+
set_cross_product_matrix_jacobian(H.block<9, 3>(27, 3), 1.0); // ∂²n/∂d∂b
188+
// ∂²n/∂d∂c = 0
189+
// ∂²n/∂d² = 0
190+
191+
return H.reshaped(3, 144);
192+
}
193+
194+
Eigen::Matrix<double, 3, 144> edge_edge_normal_hessian(
195+
Eigen::ConstRef<Eigen::Vector3d> ea0,
196+
Eigen::ConstRef<Eigen::Vector3d> ea1,
197+
Eigen::ConstRef<Eigen::Vector3d> eb0,
198+
Eigen::ConstRef<Eigen::Vector3d> eb1)
199+
{
200+
const Eigen::Vector3d z = edge_edge_unnormalized_normal(ea0, ea1, eb0, eb1);
201+
const double z_norm2 = z.squaredNorm();
202+
const double z_norm = std::sqrt(z_norm2);
203+
const double z_norm3 = z_norm2 * z_norm;
204+
205+
const auto dz_dx =
206+
edge_edge_unnormalized_normal_jacobian(ea0, ea1, eb0, eb1);
207+
const auto d2z_dx2 =
208+
edge_edge_unnormalized_normal_hessian(ea0, ea1, eb0, eb1);
209+
210+
Eigen::Matrix<double, 3, 144> d2n_dx2;
211+
for (int k = 0; k < 144; ++k) {
212+
const int i = k / 12, j = k % 12;
213+
214+
const double alpha = z.dot(dz_dx.col(i)) / z_norm3;
215+
216+
const double dalpha_dxj =
217+
(dz_dx.col(j).dot(dz_dx.col(i)) + z.dot(d2z_dx2.col(k))
218+
- 3 * z.dot(dz_dx.col(i)) * dz_dx.col(j).dot(z) / z_norm2)
219+
/ z_norm3;
220+
221+
d2n_dx2.col(k) = -dz_dx.col(i) * z.transpose() * dz_dx.col(j) / z_norm3
222+
+ d2z_dx2.col(k) / z_norm - alpha * dz_dx.col(j) - dalpha_dxj * z;
223+
}
224+
225+
return d2n_dx2;
226+
}
227+
130228
} // namespace ipc

src/ipc/geometry/normal.hpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,16 @@ inline Eigen::Matrix<double, 3, 9> triangle_normal_jacobian(
164164
* triangle_unnormalized_normal_jacobian(a, b, c);
165165
}
166166

167+
/// @brief Computes the Hessian of the normal vector of a triangle.
168+
/// @param a The first vertex of the triangle.
169+
/// @param b The second vertex of the triangle.
170+
/// @param c The third vertex of the triangle.
171+
/// @return The Hessian of the normal vector of the triangle.
172+
Eigen::Matrix<double, 3, 81> triangle_normal_hessian(
173+
Eigen::ConstRef<Eigen::Vector3d> a,
174+
Eigen::ConstRef<Eigen::Vector3d> b,
175+
Eigen::ConstRef<Eigen::Vector3d> c);
176+
167177
/** @} */
168178

169179
/**
@@ -240,4 +250,28 @@ inline Eigen::Matrix<double, 3, 12> edge_edge_normal_jacobian(
240250
* edge_edge_unnormalized_normal_jacobian(ea0, ea1, eb0, eb1);
241251
}
242252

253+
/// @brief Computes the Hessian of the unnormalized normal vector of two edges.
254+
/// @param ea0 The first vertex of the first edge.
255+
/// @param ea1 The second vertex of the first edge.
256+
/// @param eb0 The first vertex of the second edge.
257+
/// @param eb1 The second vertex of the second edge.
258+
/// @return The Hessian of the unnormalized normal vector of the two edges.
259+
Eigen::Matrix<double, 3, 144> edge_edge_unnormalized_normal_hessian(
260+
Eigen::ConstRef<Eigen::Vector3d> ea0,
261+
Eigen::ConstRef<Eigen::Vector3d> ea1,
262+
Eigen::ConstRef<Eigen::Vector3d> eb0,
263+
Eigen::ConstRef<Eigen::Vector3d> eb1);
264+
265+
/// @brief Computes the Hessian of the normal vector of two edges.
266+
/// @param ea0 The first vertex of the first edge.
267+
/// @param ea1 The second vertex of the first edge.
268+
/// @param eb0 The first vertex of the second edge.
269+
/// @param eb1 The second vertex of the second edge.
270+
/// @return The Hessian of the normal vector of the two edges.
271+
Eigen::Matrix<double, 3, 144> edge_edge_normal_hessian(
272+
Eigen::ConstRef<Eigen::Vector3d> ea0,
273+
Eigen::ConstRef<Eigen::Vector3d> ea1,
274+
Eigen::ConstRef<Eigen::Vector3d> eb0,
275+
Eigen::ConstRef<Eigen::Vector3d> eb1);
276+
243277
} // namespace ipc

0 commit comments

Comments
 (0)