diff --git a/include/hpp/fcl/collision_data.h b/include/hpp/fcl/collision_data.h index c95f78777cff589be5e97328be0c4dd8f6d3e3f7..bf08074c6647314fb7377a63ce37e40265c61b33 100644 --- a/include/hpp/fcl/collision_data.h +++ b/include/hpp/fcl/collision_data.h @@ -489,6 +489,216 @@ struct HPP_FCL_DLLAPI CollisionResult : QueryResult { void swapObjects(); }; +/// @brief Request for a contact patch computation. +/// @note Please see the doc of `ContactPatch` to understand what is meant by +/// "ContactPatch". +struct HPP_FCL_DLLAPI ContactPatchRequest { + /// @brief The maximum number of contact patches that can be computed. + size_t num_max_contact_patches; + + /// @brief Maximum number of vertices of each contact patch. + size_t max_size_contact_patch; + + explicit ContactPatchRequest(size_t num_max_contact_patches = 1, + size_t max_size_contact_patch = 1) + : num_max_contact_patches(num_max_contact_patches), + max_size_contact_patch(max_size_contact_patch) {} +}; + +/// @brief Contact patch information. +/// A contact patch has a normal `n = Contact::normal` and passes by `p = +/// Contact::pos`. If we denote by P the plane passing by `p` and supported by +/// `n`, a contact patch is represented as a polytope which vertices all belong +/// to `P & S1 & S2`, where `&` denotes the set-intersection. +/// @tparam Dimension of the contact patch. A contact patch can store 3D points +/// (typically used in `ContactPatchResult`), but also 2D points (typically used +/// internally by hpp-fcl when calling `computeContactPatch`). +/// +/// In details: +/// PART I - What is a contact point? +/// --------------------------------- +/// This section is important to understand what is a contact patch. +/// When calling `collide` on a collision pair, the `CollisionResult` +/// given as an input is populated with a vector of `Contact` (see @ref +/// CollisionResult and @ref Contact). +/// These structures encode the fact that S1 and S2 make contact at the +/// location `p = Contact::pos`, with normal `n = Contact::normal` and +/// penetration depth `d = Contact::penetration_depth`. +/// The tuple `(p, n, d)` has remarkable mathematical properties because we +/// always have: +/// - `p1 = p - d*n` is a witness point, and belongs to the surface of S1, +/// - `p2 = p + d*n` is a witness point, and belongs to the surface of S2. +/// - The pair `(p1, p2)` defines a pair of witness points such that +/// translating S1 by `p1 - p2` separates the shapes. The vector `v = p1 - p2` +/// is called a separating vector. +/// - Remarkably, if `w` is another separating vector, then **necessarily** +/// `||w|| >= ||v||`. +/// --> We call a "smallest separating vector" a separating vector `v` such that +/// `||v|| = |d|`. Any smallest separating vector `v` has a norm `||v|| = |d|`. +/// --> Finally, we call the "separating plane" `P` the plane supported by `n`, +/// and passing by `p`. This plane `P` can easily be translated by `+- v/2`; +/// this gives two planes tangent to the shapes S1 and S2, supported by `n` and +/// passing by `p1` and `p2` respectively. +/// +/// PART II - What is a contact patch? +/// ---------------------------------- +/// Up to this point, we have a normal which only maps to one contact point. +/// However, imagine a scenario where a box is sitting on a plane. Alghouth the +/// normal `n` is unique, the point `p` stored in `Contact::pos` and computed by +/// `collide` is arbitrary. +/// Hence, a contact patch is simply a surface (or volume) of contact between +/// two shapes S1 and S2. This contact surface is nothing more than the (triple) +/// intersection between the separating plane (see PART I), the shape S1 and the +/// shape S2, `P & S1 & S2`. If the contact patch is a volume, it is (an +/// approximation of) the intersection between S1 and S2. +/// TODO(louis): modify `computeContactPatch` to approximate the intersection +/// volume. +/// +/// PART III - Can there be more than one contact patch? +/// ---------------------------------------------------- +/// Yes, but again it is very rare in practice, as there needs to be more than +/// one smallest separation vector. +/// However, imagine two identical boxes (i.e. Box(1, 1, 1)) located at the +/// exact same pose in space. There are as many smallest separation vectors as +/// there are faces in a box, i.e. there are 6 smallest separation vectors. So +/// there can be at most 6 `hpp::fcl::Contact`. The HPP-FCL library implements +/// GJK and EPA and allows to recover up to `CollisionRequest::num_max_contacts` +/// smallest separation vectors. So in this scenario, we could recover the 6 +/// normals (and contact position and penetration depth). Then, we can recover +/// the 6 contact patches. +/// TODO(louis): modify EPA to recover the entire optimal set. +/// +template <int Dimension = 3> +struct HPP_FCL_DLLAPI ContactPatchTpl { + public: + using MatrixxDf = + Eigen::Matrix<FCL_REAL, Eigen::Dynamic, Dimension, Eigen::RowMajor>; + using MatrixxDfBlock = + Eigen::Block<MatrixxDf, Eigen::Dynamic, Dimension, true>; + using ConstMatrixxDfBlock = + Eigen::Block<const MatrixxDf, Eigen::Dynamic, 3, true>; + using Index = Eigen::Index; + + /// @brief Normal of the contact patch. + Vec3f normal; + + /// @brief Default maximum size of the polytope representing the contact + /// patch. + static constexpr size_t default_max_size = 6; + + private: + /// @brief Vertices of the polytope in the `ContactPatch`. + /// @note For now (April 2024), a `ContactPatch` is a 2D polytope, + /// so the vertices, forming the convex-hull of the polytope, are stored in a + /// counter-clockwise fashion. + Matrixx3f m_contact_points; + + /// @brief Current size of the contact patch. + Index m_size; + + public: + /// @brief Default constructor. + explicit ContactPatchTpl(size_t max_size = default_max_size) + : m_contact_points(max_size, 3), m_size(0) { + this->normal = Vec3f::Constant(std::numeric_limits<FCL_REAL>::quiet_NaN()); + } + + /// @brief Maximum size of the contact patch. + size_t getMaximumSize() const { + return (size_t)(this->m_contact_points.rows()); + } + + /// @brief Current size of the contact patch. + size_t getSize() const { return (size_t)(this->m_size); } + + /// @brief Update the maximum size of the contact patch. + /// @note This clears the content of the contact patch. + void updateMaxSize(const size_t max_size) { + this->m_contact_points.resize((Index)max_size, (Index)3); + this->m_size = 0; + } + + /// @brief Insert a contact point in the contact patch. + void insert(const Vec3f& contact_point) { + HPP_FCL_ASSERT(this->m_size < this->m_contact_points.rows(), + "Tried to insert point in contact patch but exceeded " + "maximum size of contact patch.", + std::logic_error); + this->m_contact_points.row(this->m_size) = contact_point; + ++(this->m_size); + } + + /// @brief Getter for the contact points in the contact patch. + MatrixxDfBlock contactPoints() { + return this->m_contact_points.topRows(this->m_size); + } + + /// @brief Const getter for the contact points in the contact patch. + ConstMatrixxDfBlock contactPoints() const { + return this->m_contact_points.topRows(this->m_size); + } + + /// @brief Insert a contact point in the contact patch. + void clear() { this->m_size = 0; } +}; + +/// @brief Default contact patch storing 3D information (i.e. 3D points in +/// space). Please see @ref ContactPatchTpl. +/// @note As of now (April 2024), this 3D information is a 2D contact surface in +/// 3D but internal algorithms of hpp-fcl can easily be extended to compute a +/// volume of contact instead. +using ContactPatch = ContactPatchTpl<3>; + +/// @brief Result for a contact patch computation. +struct HPP_FCL_DLLAPI ContactPatchResult { + using ContactPatchVector = std::vector<ContactPatch>; + using ContactPatchRef = std::reference_wrapper<ContactPatch>; + using ContactPatchRefVector = std::vector<ContactPatchRef>; + + private: + /// @brief Data container for the vector of contact patches. + /// @note Contrary to `CollisionResult` or `DistanceResult`, which have a very + /// small memory footprint, contact patches can contain relatively large + /// polytopes. In order to reuse a `ContactPatchResult` while avoiding + /// successive mallocs, we have a data container and a vector which points to + /// the currently active patches in this data container. + ContactPatchVector m_contact_patches_data; + + public: + /// @brief Vector of contact patches of the result. + ContactPatchRefVector contact_patches; + + /// @brief Default constructor. + ContactPatchResult() = default; + + /// @brief Constructor using a `ContactPatchRequest`. + explicit ContactPatchResult(const ContactPatchRequest& request) { + this->initialize(request); + }; + + /// @brief Number of contact patches in the result. + size_t numContactPatches() const { return this->contact_patches.size(); } + + /// @biref Clears the contact patch result. + void clear() { + this->contact_patches.clear(); + for (ContactPatch& patch : this->m_contact_patches_data) { + patch.clear(); + } + } + + /// @brief Initializes a `ContactPatchResult` from a `ContactPatchRequest` + void initialize(const ContactPatchRequest& request) { + if (this->contact_patches.size() < request.num_max_contact_patches) { + this->m_contact_patches_data.resize(request.num_max_contact_patches); + } + for (ContactPatch& patch : this->m_contact_patches_data) { + patch.updateMaxSize(request.max_size_contact_patch); + } + this->clear(); + } +}; + struct DistanceResult; /// @brief request to the distance computation