Skip to content

Commit 6cf05c9

Browse files
committed
Update Nonlinear CCD Docs
1 parent b28b3c0 commit 6cf05c9

File tree

12 files changed

+211
-56
lines changed

12 files changed

+211
-56
lines changed

docs/source/cpp-api/interval.rst

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
Interval Arithmetic
2+
===================
3+
4+
For interval arithmetic, we use the `Filib <https://github.com/zfergus/filib>`_ library.
5+
6+
.. doxygenclass:: filib::Interval
7+
8+
Helper Functions
9+
----------------
10+
11+
.. doxygenfunction:: ipc::norm
12+
.. doxygenfunction:: ipc::squared_norm
13+
14+
Type Definitions
15+
----------------
16+
17+
.. doxygentypedef:: ipc::Vector2I
18+
.. doxygentypedef:: ipc::Vector3I
19+
.. doxygentypedef:: ipc::VectorXI
20+
21+
.. doxygentypedef:: ipc::RowVector2I
22+
.. doxygentypedef:: ipc::RowVector3I
23+
.. doxygentypedef:: ipc::RowVectorXI
24+
25+
.. doxygentypedef:: ipc::Matrix2I
26+
.. doxygentypedef:: ipc::Matrix3I
27+
.. doxygentypedef:: ipc::MatrixXI
28+
29+
.. doxygentypedef:: ipc::VectorMax3I
30+
.. doxygentypedef:: ipc::RowVectorMax3I
31+
.. doxygentypedef:: ipc::MatrixMax3I

docs/source/cpp.rst

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ C++
3131
:start-after: <!--- BEGIN C++ README 2 --->
3232
:end-before: <!--- FILIB DEPENDENCY NOTE --->
3333

34+
.. _filib_dependency_note:
35+
3436
.. warning::
3537
``filib`` is licensed under `LGPL-2.1 <https://github.com/zfergus/filib/blob/main/LICENSE>`_ and as such it is required to be dynamically linked. Doing so automatically is a challenge, so by default we use static linkage. Enabling dynaic linkage requires copying the ``.so``/``.dylib``/``.dll`` file to the binary directory or system path. To enable this, set the CMake option :cmake:`FILIB_BUILD_SHARED_LIBS` to :cmake:`ON` and add this CMake code to copy the shared libaray object to the binary directory:
3638

docs/source/index.rst

+2
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
cpp-api/barrier.rst
3838
cpp-api/adhesion.rst
3939
cpp-api/intersections.rst
40+
cpp-api/interval.rst
4041
cpp-api/utils.rst
4142

4243
.. toctree::
@@ -57,6 +58,7 @@
5758
python-api/barrier.rst
5859
python-api/adhesion.rst
5960
python-api/intersections.rst
61+
python-api/interval.rst
6062
python-api/utils.rst
6163

6264
.. toctree::

docs/source/python-api/interval.rst

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Interval Arithmetic
2+
===================
3+
4+
.. automodule:: ipctk.filib
5+
:members:
6+
:undoc-members:
7+
:show-inheritance:
8+
:inherited-members:

docs/source/tutorial/adhesion.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ Adhesion
33

44
Adhesion simulates the sticking interaction between surfaces, such as glue bonding or material contact.
55

6-
We provide functionality to compute normal adhesion (perpendicular forces) and tangential adhesion (parallel forces) using the model of :cite:`Fang2023AugmentedStickyInteractions`.
6+
We provide functionality to compute normal adhesion (perpendicular forces) and tangential adhesion (parallel forces) using the model of :cite:t:`Fang2023AugmentedStickyInteractions`.
77

88
Normal Adhesion
99
---------------

docs/source/tutorial/nonlinear_ccd.rst

+101-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
Nonlinear CCD
22
=============
33

4-
We can also perform CCD of nonlinear trajectories (of linear geometry) using the method of :cite:t:`Ferguson2021RigidIPC`. While :cite:t:`Ferguson2021RigidIPC` introduce their method in the context of rigid bodies, it can be applied to any nonlinear trajectory of linear geometry. The method works by transforming the nonlinear trajectories into (adaptive) piecewise linear trajectories with an envelope/minimum separation around each piece, enclosing the nonlinear trajectory. The method then performs CCD on the piecewise linear trajectories to find the earliest time of impact.
4+
We also implement CCD of nonlinear trajectories (of linear geometry) using the method of :cite:t:`Ferguson2021RigidIPC`. While :cite:t:`Ferguson2021RigidIPC` introduce their method in the context of rigid bodies, it can be applied to any nonlinear trajectory of linear geometry.
5+
6+
The method works by transforming the nonlinear trajectories into (adaptive) piecewise linear trajectories with an envelope/minimum separation around each piece, enclosing the nonlinear trajectory. The method then performs CCD on the piecewise linear trajectories to find the earliest time of impact.
57

68
We provide the following functions to perform nonlinear CCD:
79

@@ -14,7 +16,7 @@ We provide the following functions to perform nonlinear CCD:
1416
* :cpp:func:`ipc::edge_edge_nonlinear_ccd`, and
1517
* :cpp:func:`ipc::point_triangle_nonlinear_ccd`.
1618

17-
Each of these functions take as input a :cpp:func:`ipc::NonlinearTrajectory` object for the endpoints of the linear geometry.
19+
Each of these functions take as input a :cpp:class:`ipc::NonlinearTrajectory` object for the endpoints of the linear geometry.
1820

1921
.. md-tab-item:: Python
2022

@@ -23,7 +25,7 @@ We provide the following functions to perform nonlinear CCD:
2325
* :py:func:`ipctk.edge_edge_nonlinear_ccd`, and
2426
* :py:func:`ipctk.point_triangle_nonlinear_ccd`.
2527

26-
Each of these functions take as input a :py:func:`ipctk.NonlinearTrajectory` object for the endpoints of the linear geometry.
28+
Each of these functions take as input a :py:class:`ipctk.NonlinearTrajectory` object for the endpoints of the linear geometry.
2729

2830
For example, the following code defines a rigid trajectory in 2D in order to perform nonlinear CCD between a point and edge:
2931

@@ -47,7 +49,7 @@ For example, the following code defines a rigid trajectory in 2D in order to per
4749
Defining the Trajectory
4850
-----------------------
4951

50-
Let's dive deeper by breaking down the implementation of Rigid2DTrajectory. The first function we need to implement is the call operator:
52+
Let's dive deeper by breaking down the implementation of ``Rigid2DTrajectory``. The first function we need to implement is the call operator:
5153

5254
.. md-tab-set::
5355

@@ -63,7 +65,7 @@ Let's dive deeper by breaking down the implementation of Rigid2DTrajectory. The
6365

6466
.. literalinclude:: ../../../python/tests/test_ccd.py
6567
:language: python
66-
:lines: 91-95
68+
:lines: 90-94
6769
:dedent: 8
6870

6971
This function computes the position of the point at a time :math:`t \in [0, 1]`. This defines the trajectory of the point. In this case, we have a rigid body with a center of mass (COM) at the origin. The trajectory of the point is given by:
@@ -92,7 +94,7 @@ The second function we need to implement is ``max_distance_from_linear``.
9294

9395
.. literalinclude:: ../../../python/tests/test_ccd.py
9496
:language: python
95-
:lines: 97-103
97+
:lines: 96-102
9698
:dedent: 8
9799

98100
This function computes the maximum distance over a time interval :math:`[t_0, t_1]` between the nonlinear trajectory and a line segment from :math:`x(t_0)` to :math:`x(t_1)`. Mathematically this function computes
@@ -135,4 +137,96 @@ Last, we use the ``Rigid2DTrajectory`` to perform nonlinear CCD between a point
135137
:dedent: 4
136138

137139
.. note::
138-
We adjust the ``conservative_rescaling`` parameter to get a more accurate time of impact (TOI), but in practice, this is not needed as a more conservative estimate of the TOI is sufficient to avoid penetrations.
140+
We adjust the ``conservative_rescaling`` parameter to get a more accurate time of impact (TOI), but in practice, this is not needed as a more conservative estimate of the TOI is sufficient to avoid penetrations.
141+
142+
Interval-Based Nonlinear CCD
143+
----------------------------
144+
145+
.. warning::
146+
The following section requires enabling the ``filib`` interval arithmetic library. ``filib`` is licensed under L-GPL 2.1, so special care must be taken when using it. See the `filib dependency note <../../cpp.html#filib-dependency-note>`_ for more information.
147+
148+
If an analytic expression for the ``max_distance_from_linear`` function is not available, we can use interval arithmetic to compute a conservative envelope.
149+
150+
.. md-tab-set::
151+
152+
.. md-tab-item:: C++
153+
154+
The :cpp:class:`ipc::IntervalNonlinearTrajectory` class does this for us and all we need to provide is a function to compute the point's position over a time interval.
155+
156+
.. md-tab-item:: Python
157+
158+
The :py:class:`ipctk.IntervalNonlinearTrajectory` class does this for us and all we need to provide is a function to compute the point's position over a time interval.
159+
160+
Conservative Envelope
161+
~~~~~~~~~~~~~~~~~~~~~
162+
163+
Our implementation of the ``max_distance_from_linear`` function is as follows.
164+
165+
Start by defining a linear interpolation function:
166+
167+
.. math::
168+
\operatorname{lerp}(a, b, t) := (b - a) t + a,
169+
170+
which interpolates between two points :math:`a` and :math:`b` at time :math:`t`.
171+
172+
The exact envelope from above is bounded by a interval approximation:
173+
174+
.. math::
175+
\begin{align}
176+
&\max_{t \in [0, 1]} \| p(\operatorname{lerp}(t_0, t_1, t)) - \operatorname{lerp}(p(t_0), p(t_1), t) \|_2\\
177+
&\leq \sup(\| p_{\Box}([t_0, t_1]) - ((p(t_1) - p(t_0)) \cdot [0, 1] + p(t_0)) \|_2).
178+
\end{align}
179+
180+
Therefore, we can compute the a conservative estimate of the envelope by implementing :math:`p_{\Box}([t_0, t_1])` and :math:`p(t)`.
181+
182+
.. note::
183+
In practice we subdivide the interval into smaller intervals and compute the envelope over each subinterval. This is done to create a more accurate estimate.
184+
185+
Interval Arithmetic
186+
~~~~~~~~~~~~~~~~~~~
187+
188+
`Interval arithmetic <https://en.wikipedia.org/wiki/Interval_arithmetic>`_ is a method to compute bounds on the range of a function over an interval. We can construct a vector of intervals to represent the position of the point over a time interval.
189+
190+
The following code snippet shows an example of how to use interval arithmetic to compute the position of a point over a time interval:
191+
192+
.. md-tab-set::
193+
194+
.. md-tab-item:: C++
195+
196+
.. code-block:: cpp
197+
198+
#include <ipc/utils/interval.hpp>
199+
200+
using namespace ipc;
201+
202+
Vector2I position(
203+
const VectorMax3d& center,
204+
const VectorMax3d& point,
205+
const double omega,
206+
const Interval& t)
207+
{
208+
// 2×2 matrix of intervals representing the rotation matrix
209+
Matrix2I R;
210+
R << cos(omega * t), -sin(omega * t),
211+
sin(omega * t), cos(omega * t);
212+
return R * (point - center) + center;
213+
}
214+
215+
The full documentation for the ``Interval`` class can be found `here <../../cpp-api/interval.html>`_.
216+
217+
.. md-tab-item:: Python
218+
219+
.. code-block:: python
220+
221+
import numpy as np
222+
223+
from ipctk.filib import Interval, sin, cos
224+
225+
def position(center, point, omega, t : Interval):
226+
R = np.array([
227+
[cos(omega * t), -sin(omega * t)],
228+
[sin(omega * t), cos(omega * t)]
229+
])
230+
return R @ (point - center) + center
231+
232+
The full documentation for the filib python bindings can be found `here <../python-api/interval.html>`_.

python/src/utils/interval.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ using namespace filib;
3636
void define_interval(py::module_& m)
3737
{
3838
#ifdef IPC_TOOLKIT_WITH_FILIB
39-
auto m_filib = m.def_submodule("filib", "Fast Interval Library");
39+
auto m_filib = m.def_submodule(
40+
"filib",
41+
"Python bindings for the `Fast Interval Library (Filib) <https://github.com/zfergus/filib>`_ by Werner Hofschuster and Walter Kraemer.");
4042

4143
py::class_<Interval>(m_filib, "Interval")
4244
.def(py::init())

src/ipc/candidates/collision_stencil.hpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class CollisionStencil {
3939
/// @return The vertex positions of the collision stencil. Size is always 4, but elements i > num_vertices() are NaN.
4040
template <typename T>
4141
std::array<VectorMax3<T>, 4> vertices(
42-
const MatrixX<T>& vertices,
42+
const Eigen::MatrixX<T>& vertices,
4343
const Eigen::MatrixXi& edges,
4444
const Eigen::MatrixXi& faces) const
4545
{
@@ -67,7 +67,7 @@ class CollisionStencil {
6767
/// @return This stencil's DOF.
6868
template <typename T>
6969
VectorMax12<T>
70-
dof(const MatrixX<T>& X,
70+
dof(const Eigen::MatrixX<T>& X,
7171
const Eigen::MatrixXi& edges,
7272
const Eigen::MatrixXi& faces) const
7373
{

src/ipc/collisions/normal/normal_collisions.cpp

+4-3
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ namespace {
136136

137137
return ev_candidates;
138138
}
139+
140+
inline double sqr(double x) { return x * x; }
139141
} // namespace
140142

141143
void NormalCollisions::build(
@@ -147,7 +149,7 @@ void NormalCollisions::build(
147149
{
148150
assert(vertices.rows() == mesh.num_vertices());
149151

150-
double inflation_radius = (dhat + dmin) / 2;
152+
const double inflation_radius = 0.5 * (dhat + dmin);
151153

152154
Candidates candidates;
153155
candidates.build(mesh, vertices, inflation_radius, broad_phase_method);
@@ -168,8 +170,7 @@ void NormalCollisions::build(
168170

169171
// Cull the candidates by measuring the distance and dropping those that are
170172
// greater than dhat.
171-
const double offset_sqr = (dmin + dhat) * (dmin + dhat);
172-
auto is_active = [&](double distance_sqr) {
173+
auto is_active = [offset_sqr = sqr(dmin + dhat)](double distance_sqr) {
173174
return distance_sqr < offset_sqr;
174175
};
175176

src/ipc/tangent/closest_point.cpp

+5-5
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ VectorMax9d point_edge_closest_point_jacobian(
4040
// ============================================================================
4141
// Edge - Edge
4242

43-
Vector2<double> edge_edge_closest_point(
43+
Eigen::Vector2d edge_edge_closest_point(
4444
const Eigen::Ref<const Eigen::Vector3d>& ea0,
4545
const Eigen::Ref<const Eigen::Vector3d>& ea1,
4646
const Eigen::Ref<const Eigen::Vector3d>& eb0,
@@ -55,7 +55,7 @@ Vector2<double> edge_edge_closest_point(
5555
coefMtr(0, 1) = coefMtr(1, 0) = -eb.dot(ea);
5656
coefMtr(1, 1) = eb.squaredNorm();
5757

58-
Vector2<double> rhs;
58+
Eigen::Vector2d rhs;
5959
rhs[0] = -eb_to_ea.dot(ea);
6060
rhs[1] = eb_to_ea.dot(eb);
6161

@@ -82,15 +82,15 @@ Eigen::Matrix<double, 2, 12> edge_edge_closest_point_jacobian(
8282
// ============================================================================
8383
// Point - Triangle
8484

85-
Vector2<double> point_triangle_closest_point(
85+
Eigen::Vector2d point_triangle_closest_point(
8686
const Eigen::Ref<const Eigen::Vector3d>& p,
8787
const Eigen::Ref<const Eigen::Vector3d>& t0,
8888
const Eigen::Ref<const Eigen::Vector3d>& t1,
8989
const Eigen::Ref<const Eigen::Vector3d>& t2)
9090
{
9191
Eigen::Matrix<double, 2, 3> basis;
92-
basis.row(0) = RowVector3<double>(t1 - t0); // edge 0
93-
basis.row(1) = RowVector3<double>(t2 - t0); // edge 1
92+
basis.row(0) = Eigen::RowVector3d(t1 - t0); // edge 0
93+
basis.row(1) = Eigen::RowVector3d(t2 - t0); // edge 1
9494
const Eigen::Matrix2d A = basis * basis.transpose();
9595
const Eigen::Vector2d b = basis * (p - t0);
9696
const Eigen::Vector2d x = A.ldlt().solve(b);

src/ipc/utils/eigen_ext.hpp

+8-19
Original file line numberDiff line numberDiff line change
@@ -8,31 +8,20 @@
88
namespace ipc {
99

1010
// Boolean scalar
11-
typedef Eigen::Array<bool, Eigen::Dynamic, 1> ArrayXb;
12-
typedef Eigen::Matrix<bool, Eigen::Dynamic, 1> VectorXb;
13-
typedef Eigen::Matrix<bool, 3, 1> Vector3b;
14-
typedef Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic> MatrixXb;
11+
using ArrayXb = Eigen::Array<bool, Eigen::Dynamic, 1>;
12+
using VectorXb = Eigen::Matrix<bool, Eigen::Dynamic, 1>;
13+
using Vector3b = Eigen::Matrix<bool, 3, 1>;
14+
using MatrixXb = Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic>;
1515

1616
template <typename T, int dim, int max_dim = dim>
1717
using Vector = Eigen::Matrix<T, dim, 1, Eigen::ColMajor, max_dim, 1>;
1818
template <typename T, int dim, int max_dim = dim>
1919
using RowVector = Eigen::Matrix<T, 1, dim, Eigen::RowMajor, 1, max_dim>;
20-
template <typename T> using Vector1 = Vector<T, 1>;
21-
template <typename T> using Vector2 = Vector<T, 2>;
22-
template <typename T> using Vector3 = Vector<T, 3>;
23-
template <typename T> using VectorX = Vector<T, Eigen::Dynamic>;
24-
template <typename T> using RowVector2 = RowVector<T, 2>;
25-
template <typename T> using RowVector3 = RowVector<T, 3>;
26-
template <typename T> using RowVectorX = RowVector<T, Eigen::Dynamic>;
27-
template <typename T> using Matrix2 = Eigen::Matrix<T, 2, 2>;
28-
template <typename T> using Matrix3 = Eigen::Matrix<T, 3, 3>;
29-
template <typename T>
30-
using MatrixX = Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>;
3120

32-
using Vector1d = Vector1<double>;
33-
using Vector6d = Vector<double, 6>;
34-
using Vector9d = Vector<double, 9>;
35-
using Vector12d = Vector<double, 12>;
21+
using Vector1d = Eigen::Vector<double, 1>;
22+
using Vector6d = Eigen::Vector<double, 6>;
23+
using Vector9d = Eigen::Vector<double, 9>;
24+
using Vector12d = Eigen::Vector<double, 12>;
3625
using Matrix6d = Eigen::Matrix<double, 6, 6>;
3726
using Matrix9d = Eigen::Matrix<double, 9, 9>;
3827
using Matrix12d = Eigen::Matrix<double, 12, 12>;

0 commit comments

Comments
 (0)