Program Listing for File narrowphase.h
↰ Return to documentation for file (include/coal/narrowphase/narrowphase.h
)
/*
* Software License Agreement (BSD License)
*
* Copyright (c) 2011-2014, Willow Garage, Inc.
* Copyright (c) 2014-2015, Open Source Robotics Foundation
* Copyright (c) 2018-2019, Centre National de la Recherche Scientifique
* All rights reserved.
* Copyright (c) 2021-2024, INRIA
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials provided
* with the distribution.
* * Neither the name of Open Source Robotics Foundation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef COAL_NARROWPHASE_H
#define COAL_NARROWPHASE_H
#include <limits>
#include "coal/narrowphase/gjk.h"
#include "coal/collision_data.h"
#include "coal/narrowphase/narrowphase_defaults.h"
#include "coal/logging.h"
namespace coal {
struct COAL_DLLAPI GJKSolver {
public:
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
mutable details::GJK gjk;
size_t gjk_max_iterations;
CoalScalar gjk_tolerance;
GJKInitialGuess gjk_initial_guess;
COAL_DEPRECATED_MESSAGE(Use gjk_initial_guess instead)
bool enable_cached_guess;
mutable Vec3s cached_guess;
mutable support_func_guess_t support_func_cached_guess;
CoalScalar distance_upper_bound;
GJKVariant gjk_variant;
GJKConvergenceCriterion gjk_convergence_criterion;
GJKConvergenceCriterionType gjk_convergence_criterion_type;
mutable details::EPA epa;
size_t epa_max_iterations;
CoalScalar epa_tolerance;
mutable details::MinkowskiDiff minkowski_difference;
private:
// Used internally for assertion checks.
static constexpr CoalScalar m_dummy_precision = 1e-6;
public:
COAL_COMPILER_DIAGNOSTIC_PUSH
COAL_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS
GJKSolver()
: gjk(GJK_DEFAULT_MAX_ITERATIONS, GJK_DEFAULT_TOLERANCE),
gjk_max_iterations(GJK_DEFAULT_MAX_ITERATIONS),
gjk_tolerance(GJK_DEFAULT_TOLERANCE),
gjk_initial_guess(GJKInitialGuess::DefaultGuess),
enable_cached_guess(false), // Use gjk_initial_guess instead
cached_guess(Vec3s(1, 0, 0)),
support_func_cached_guess(support_func_guess_t::Zero()),
distance_upper_bound((std::numeric_limits<CoalScalar>::max)()),
gjk_variant(GJKVariant::DefaultGJK),
gjk_convergence_criterion(GJKConvergenceCriterion::Default),
gjk_convergence_criterion_type(GJKConvergenceCriterionType::Absolute),
epa(0, EPA_DEFAULT_TOLERANCE),
epa_max_iterations(EPA_DEFAULT_MAX_ITERATIONS),
epa_tolerance(EPA_DEFAULT_TOLERANCE) {}
explicit GJKSolver(const DistanceRequest& request)
: gjk(request.gjk_max_iterations, request.gjk_tolerance),
epa(0, request.epa_tolerance) {
this->cached_guess = Vec3s(1, 0, 0);
this->support_func_cached_guess = support_func_guess_t::Zero();
set(request);
}
void set(const DistanceRequest& request) {
// ---------------------
// GJK settings
this->gjk_initial_guess = request.gjk_initial_guess;
this->enable_cached_guess = request.enable_cached_gjk_guess;
if (this->gjk_initial_guess == GJKInitialGuess::CachedGuess ||
this->enable_cached_guess) {
this->cached_guess = request.cached_gjk_guess;
this->support_func_cached_guess = request.cached_support_func_guess;
}
this->gjk_max_iterations = request.gjk_max_iterations;
this->gjk_tolerance = request.gjk_tolerance;
// For distance computation, we don't want GJK to early stop
this->distance_upper_bound = (std::numeric_limits<CoalScalar>::max)();
this->gjk_variant = request.gjk_variant;
this->gjk_convergence_criterion = request.gjk_convergence_criterion;
this->gjk_convergence_criterion_type =
request.gjk_convergence_criterion_type;
// ---------------------
// EPA settings
this->epa_max_iterations = request.epa_max_iterations;
this->epa_tolerance = request.epa_tolerance;
// ---------------------
// Reset GJK and EPA status
this->epa.status = details::EPA::Status::DidNotRun;
this->gjk.status = details::GJK::Status::DidNotRun;
}
explicit GJKSolver(const CollisionRequest& request)
: gjk(request.gjk_max_iterations, request.gjk_tolerance),
epa(0, request.epa_tolerance) {
this->cached_guess = Vec3s(1, 0, 0);
this->support_func_cached_guess = support_func_guess_t::Zero();
set(request);
}
void set(const CollisionRequest& request) {
// ---------------------
// GJK settings
this->gjk_initial_guess = request.gjk_initial_guess;
this->enable_cached_guess = request.enable_cached_gjk_guess;
if (this->gjk_initial_guess == GJKInitialGuess::CachedGuess ||
this->enable_cached_guess) {
this->cached_guess = request.cached_gjk_guess;
this->support_func_cached_guess = request.cached_support_func_guess;
}
this->gjk_tolerance = request.gjk_tolerance;
this->gjk_max_iterations = request.gjk_max_iterations;
// The distance upper bound should be at least greater to the requested
// security margin. Otherwise, we will likely miss some collisions.
this->distance_upper_bound = (std::max)(
0., (std::max)(request.distance_upper_bound, request.security_margin));
this->gjk_variant = request.gjk_variant;
this->gjk_convergence_criterion = request.gjk_convergence_criterion;
this->gjk_convergence_criterion_type =
request.gjk_convergence_criterion_type;
// ---------------------
// EPA settings
this->epa_max_iterations = request.epa_max_iterations;
this->epa_tolerance = request.epa_tolerance;
// ---------------------
// Reset GJK and EPA status
this->gjk.status = details::GJK::Status::DidNotRun;
this->epa.status = details::EPA::Status::DidNotRun;
}
GJKSolver(const GJKSolver& other) = default;
COAL_COMPILER_DIAGNOSTIC_PUSH
COAL_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS
bool operator==(const GJKSolver& other) const {
return this->enable_cached_guess ==
other.enable_cached_guess && // use gjk_initial_guess instead
this->cached_guess == other.cached_guess &&
this->support_func_cached_guess == other.support_func_cached_guess &&
this->gjk_max_iterations == other.gjk_max_iterations &&
this->gjk_tolerance == other.gjk_tolerance &&
this->distance_upper_bound == other.distance_upper_bound &&
this->gjk_variant == other.gjk_variant &&
this->gjk_convergence_criterion == other.gjk_convergence_criterion &&
this->gjk_convergence_criterion_type ==
other.gjk_convergence_criterion_type &&
this->gjk_initial_guess == other.gjk_initial_guess &&
this->epa_max_iterations == other.epa_max_iterations &&
this->epa_tolerance == other.epa_tolerance;
}
COAL_COMPILER_DIAGNOSTIC_POP
bool operator!=(const GJKSolver& other) const { return !(*this == other); }
CoalScalar getDistancePrecision(const bool compute_penetration) const {
return compute_penetration
? (std::max)(this->gjk_tolerance, this->epa_tolerance)
: this->gjk_tolerance;
}
template <typename S1, typename S2>
CoalScalar shapeDistance(const S1& s1, const Transform3s& tf1, const S2& s2,
const Transform3s& tf2,
const bool compute_penetration, Vec3s& p1, Vec3s& p2,
Vec3s& normal) const {
constexpr bool relative_transformation_already_computed = false;
CoalScalar distance;
this->runGJKAndEPA(s1, tf1, s2, tf2, compute_penetration, distance, p1, p2,
normal, relative_transformation_already_computed);
return distance;
}
template <typename S1>
CoalScalar shapeDistance(const S1& s1, const Transform3s& tf1,
const TriangleP& s2, const Transform3s& tf2,
const bool compute_penetration, Vec3s& p1, Vec3s& p2,
Vec3s& normal) const {
const Transform3s tf_1M2(tf1.inverseTimes(tf2));
TriangleP tri(tf_1M2.transform(s2.a), tf_1M2.transform(s2.b),
tf_1M2.transform(s2.c));
constexpr bool relative_transformation_already_computed = true;
CoalScalar distance;
this->runGJKAndEPA(s1, tf1, tri, tf_1M2, compute_penetration, distance, p1,
p2, normal, relative_transformation_already_computed);
return distance;
}
template <typename S2>
CoalScalar shapeDistance(const TriangleP& s1, const Transform3s& tf1,
const S2& s2, const Transform3s& tf2,
const bool compute_penetration, Vec3s& p1, Vec3s& p2,
Vec3s& normal) const {
CoalScalar distance = this->shapeDistance<S2>(
s2, tf2, s1, tf1, compute_penetration, p2, p1, normal);
normal = -normal;
return distance;
}
protected:
template <typename S1, typename S2>
void getGJKInitialGuess(const S1& s1, const S2& s2, Vec3s& guess,
support_func_guess_t& support_hint,
const Vec3s& default_guess = Vec3s(1, 0, 0)) const {
// There is no reason not to warm-start the support function, so we always
// do it.
support_hint = this->support_func_cached_guess;
// The following switch takes care of the GJK warm-start.
switch (gjk_initial_guess) {
case GJKInitialGuess::DefaultGuess:
guess = default_guess;
break;
case GJKInitialGuess::CachedGuess:
guess = this->cached_guess;
break;
case GJKInitialGuess::BoundingVolumeGuess:
if (s1.aabb_local.volume() < 0 || s2.aabb_local.volume() < 0) {
COAL_THROW_PRETTY(
"computeLocalAABB must have been called on the shapes before "
"using "
"GJKInitialGuess::BoundingVolumeGuess.",
std::logic_error);
}
guess.noalias() =
s1.aabb_local.center() -
(this->minkowski_difference.oR1 * s2.aabb_local.center() +
this->minkowski_difference.ot1);
break;
default:
COAL_THROW_PRETTY("Wrong initial guess for GJK.", std::logic_error);
}
// TODO: use gjk_initial_guess instead
COAL_COMPILER_DIAGNOSTIC_PUSH
COAL_COMPILER_DIAGNOSTIC_IGNORED_DEPRECECATED_DECLARATIONS
if (this->enable_cached_guess) {
guess = this->cached_guess;
}
COAL_COMPILER_DIAGNOSTIC_POP
}
template <typename S1, typename S2,
int _SupportOptions = details::SupportOptions::NoSweptSphere>
void runGJKAndEPA(
const S1& s1, const Transform3s& tf1, const S2& s2,
const Transform3s& tf2, const bool compute_penetration,
CoalScalar& distance, Vec3s& p1, Vec3s& p2, Vec3s& normal,
const bool relative_transformation_already_computed = false) const {
// Reset internal state of GJK algorithm
if (relative_transformation_already_computed)
this->minkowski_difference.set<_SupportOptions>(&s1, &s2);
else
this->minkowski_difference.set<_SupportOptions>(&s1, &s2, tf1, tf2);
this->gjk.reset(this->gjk_max_iterations, this->gjk_tolerance);
this->gjk.setDistanceEarlyBreak(this->distance_upper_bound);
this->gjk.gjk_variant = this->gjk_variant;
this->gjk.convergence_criterion = this->gjk_convergence_criterion;
this->gjk.convergence_criterion_type = this->gjk_convergence_criterion_type;
this->epa.status = details::EPA::Status::DidNotRun;
// Get initial guess for GJK: default, cached or bounding volume guess
Vec3s guess;
support_func_guess_t support_hint;
getGJKInitialGuess(*(this->minkowski_difference.shapes[0]),
*(this->minkowski_difference.shapes[1]), guess,
support_hint);
this->gjk.evaluate(this->minkowski_difference, guess, support_hint);
switch (this->gjk.status) {
case details::GJK::DidNotRun:
COAL_ASSERT(false, "GJK did not run. It should have!",
std::logic_error);
this->cached_guess = Vec3s(1, 0, 0);
this->support_func_cached_guess.setZero();
distance = -(std::numeric_limits<CoalScalar>::max)();
p1 = p2 = normal =
Vec3s::Constant(std::numeric_limits<CoalScalar>::quiet_NaN());
break;
case details::GJK::Failed:
//
// GJK ran out of iterations.
COAL_LOG_WARNING("GJK ran out of iterations.");
GJKExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::GJK::NoCollisionEarlyStopped:
//
// Case where GJK early stopped because the distance was found to be
// above the `distance_upper_bound`.
// The two witness points have no meaning.
GJKEarlyStopExtractWitnessPointsAndNormal(tf1, distance, p1, p2,
normal);
COAL_ASSERT(distance >= this->gjk.distance_upper_bound -
this->m_dummy_precision,
"The distance should be bigger than GJK's "
"`distance_upper_bound`.",
std::logic_error);
break;
case details::GJK::NoCollision:
//
// Case where GJK converged and proved that the shapes are not in
// collision, i.e their distance is above GJK's tolerance (default
// 1e-6).
GJKExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
COAL_ASSERT(std::abs((p1 - p2).norm() - distance) <=
this->gjk.getTolerance() + this->m_dummy_precision,
"The distance found by GJK should coincide with the "
"distance between the closest points.",
std::logic_error);
break;
//
// Next are the cases where GJK found the shapes to be in collision, i.e.
// their distance is below GJK's tolerance (default 1e-6).
case details::GJK::CollisionWithPenetrationInformation:
GJKExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
COAL_ASSERT(
distance <= this->gjk.getTolerance() + this->m_dummy_precision,
"The distance found by GJK should be negative or at "
"least below GJK's tolerance.",
std::logic_error);
break;
case details::GJK::Collision:
if (!compute_penetration) {
// Skip EPA and set the witness points and the normal to nans.
GJKCollisionExtractWitnessPointsAndNormal(tf1, distance, p1, p2,
normal);
} else {
//
// GJK was not enough to recover the penetration information.
// We need to run the EPA algorithm to find the witness points,
// penetration depth and the normal.
// Reset EPA algorithm. Potentially allocate memory if
// `epa_max_face_num` or `epa_max_vertex_num` are bigger than EPA's
// current storage.
this->epa.reset(this->epa_max_iterations, this->epa_tolerance);
// TODO: understand why EPA's performance is so bad on cylinders and
// cones.
this->epa.evaluate(this->gjk, -guess);
switch (epa.status) {
//
// In the following switch cases, until the "Valid" case,
// EPA either ran out of iterations, of faces or of vertices.
// The depth, witness points and the normal are still valid,
// simply not at the precision of EPA's tolerance.
// The flag `COAL_ENABLE_LOGGING` enables feebdack on these
// cases.
//
// TODO: Remove OutOfFaces and OutOfVertices statuses and simply
// compute the upper bound on max faces and max vertices as a
// function of the number of iterations.
case details::EPA::OutOfFaces:
COAL_LOG_WARNING("EPA ran out of faces.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::OutOfVertices:
COAL_LOG_WARNING("EPA ran out of vertices.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::Failed:
COAL_LOG_WARNING("EPA ran out of iterations.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::Valid:
case details::EPA::AccuracyReached:
COAL_ASSERT(
-epa.depth <= epa.getTolerance() + this->m_dummy_precision,
"EPA's penetration distance should be negative (or "
"at least below EPA's tolerance).",
std::logic_error);
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::Degenerated:
COAL_LOG_WARNING(
"EPA warning: created a polytope with a degenerated face.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::NonConvex:
COAL_LOG_WARNING(
"EPA warning: EPA got called onto non-convex shapes.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::InvalidHull:
COAL_LOG_WARNING("EPA warning: created an invalid polytope.");
EPAExtractWitnessPointsAndNormal(tf1, distance, p1, p2, normal);
break;
case details::EPA::DidNotRun:
COAL_ASSERT(false, "EPA did not run. It should have!",
std::logic_error);
COAL_LOG_ERROR("EPA error: did not run. It should have.");
EPAFailedExtractWitnessPointsAndNormal(tf1, distance, p1, p2,
normal);
break;
case details::EPA::FallBack:
COAL_ASSERT(
false,
"EPA went into fallback mode. It should never do that.",
std::logic_error);
COAL_LOG_ERROR("EPA error: FallBack.");
EPAFailedExtractWitnessPointsAndNormal(tf1, distance, p1, p2,
normal);
break;
}
}
break; // End of case details::GJK::Collision
}
}
void GJKEarlyStopExtractWitnessPointsAndNormal(const Transform3s& tf1,
CoalScalar& distance,
Vec3s& p1, Vec3s& p2,
Vec3s& normal) const {
COAL_UNUSED_VARIABLE(tf1);
// Cache gjk result for potential future call to this GJKSolver.
this->cached_guess = this->gjk.ray;
this->support_func_cached_guess = this->gjk.support_hint;
distance = this->gjk.distance;
p1 = p2 = normal =
Vec3s::Constant(std::numeric_limits<CoalScalar>::quiet_NaN());
// If we absolutely want to return some witness points, we could use
// the following code (or simply merge the early stopped case with the
// valid case below):
// gjk.getWitnessPointsAndNormal(minkowski_difference, p1, p2, normal);
// p1 = tf1.transform(p1);
// p2 = tf1.transform(p2);
// normal = tf1.getRotation() * normal;
}
void GJKExtractWitnessPointsAndNormal(const Transform3s& tf1,
CoalScalar& distance, Vec3s& p1,
Vec3s& p2, Vec3s& normal) const {
// Apart from early stopping, there are two cases where GJK says there is no
// collision:
// 1. GJK proved the distance is above its tolerance (default 1e-6).
// 2. GJK ran out of iterations.
// In any case, `gjk.ray`'s norm is bigger than GJK's tolerance and thus
// it can safely be normalized.
COAL_ASSERT(this->gjk.ray.norm() >
this->gjk.getTolerance() - this->m_dummy_precision,
"The norm of GJK's ray should be bigger than GJK's tolerance.",
std::logic_error);
// Cache gjk result for potential future call to this GJKSolver.
this->cached_guess = this->gjk.ray;
this->support_func_cached_guess = this->gjk.support_hint;
distance = this->gjk.distance;
// TODO: On degenerated case, the closest points may be non-unique.
// (i.e. an object face normal is colinear to `gjk.ray`)
gjk.getWitnessPointsAndNormal(this->minkowski_difference, p1, p2, normal);
Vec3s p = tf1.transform(0.5 * (p1 + p2));
normal = tf1.getRotation() * normal;
p1.noalias() = p - 0.5 * distance * normal;
p2.noalias() = p + 0.5 * distance * normal;
}
void GJKCollisionExtractWitnessPointsAndNormal(const Transform3s& tf1,
CoalScalar& distance,
Vec3s& p1, Vec3s& p2,
Vec3s& normal) const {
COAL_UNUSED_VARIABLE(tf1);
COAL_ASSERT(this->gjk.distance <=
this->gjk.getTolerance() + this->m_dummy_precision,
"The distance should be lower than GJK's tolerance.",
std::logic_error);
// Because GJK has returned the `Collision` status and EPA has not run,
// we purposefully do not cache the result of GJK, because ray is zero.
// However, we can cache the support function hint.
// this->cached_guess = this->gjk.ray;
this->support_func_cached_guess = this->gjk.support_hint;
distance = this->gjk.distance;
p1 = p2 = normal =
Vec3s::Constant(std::numeric_limits<CoalScalar>::quiet_NaN());
}
void EPAExtractWitnessPointsAndNormal(const Transform3s& tf1,
CoalScalar& distance, Vec3s& p1,
Vec3s& p2, Vec3s& normal) const {
// Cache EPA result for potential future call to this GJKSolver.
// This caching allows to warm-start the next GJK call.
this->cached_guess = -(this->epa.depth * this->epa.normal);
this->support_func_cached_guess = this->epa.support_hint;
distance = (std::min)(0., -this->epa.depth);
this->epa.getWitnessPointsAndNormal(this->minkowski_difference, p1, p2,
normal);
// The following is very important to understand why EPA can sometimes
// return a normal that is not colinear to the vector $p_1 - p_2$ when
// working with tolerances like $\epsilon = 10^{-3}$.
// It can be resumed with a simple idea:
// EPA is an algorithm meant to find the penetration depth and the
// normal. It is not meant to find the closest points.
// Again, the issue here is **not** the normal, it's $p_1$ and $p_2$.
//
// More details:
// We'll denote $S_1$ and $S_2$ the two shapes, $n$ the normal and $p_1$ and
// $p_2$ the witness points. In theory, when EPA converges to $\epsilon =
// 0$, the normal and witness points verify the following property (P):
// - $p_1 \in \partial \sigma_{S_1}(n)$,
// - $p_2 \in \partial \sigma_{S_2}(-n),
// where $\sigma_{S_1}$ and $\sigma_{S_2}$ are the support functions of
// $S_1$ and $S_2$. The $\partial \sigma(n)$ simply denotes the support set
// of the support function in the direction $n$. (Note: I am leaving out the
// details of frame choice for the support function, to avoid making the
// mathematical notation too heavy.)
// --> In practice, EPA converges to $\epsilon > 0$.
// On polytopes and the likes, this does not change much and the property
// given above is still valid.
// --> However, this is very different on curved surfaces, such as
// ellipsoids, cylinders, cones, capsules etc. For these shapes, converging
// at $\epsilon = 10^{-6}$ or to $\epsilon = 10^{-3}$ does not change the
// normal much, but the property (P) given above is no longer valid, which
// means that the points $p_1$ and $p_2$ do not necessarily belong to the
// support sets in the direction of $n$ and thus $n$ and $p_1 - p_2$ are not
// colinear.
//
// Do not panic! This is fine.
// Although the property above is not verified, it's almost verified,
// meaning that $p_1$ and $p_2$ belong to support sets in directions that
// are very close to $n$.
//
// Solution to compute better $p_1$ and $p_2$:
// We compute the middle points of the current $p_1$ and $p_2$ and we use
// the normal and the distance given by EPA to compute the new $p_1$ and
// $p_2$.
Vec3s p = tf1.transform(0.5 * (p1 + p2));
normal = tf1.getRotation() * normal;
p1.noalias() = p - 0.5 * distance * normal;
p2.noalias() = p + 0.5 * distance * normal;
}
void EPAFailedExtractWitnessPointsAndNormal(const Transform3s& tf1,
CoalScalar& distance, Vec3s& p1,
Vec3s& p2, Vec3s& normal) const {
this->cached_guess = Vec3s(1, 0, 0);
this->support_func_cached_guess.setZero();
COAL_UNUSED_VARIABLE(tf1);
distance = -(std::numeric_limits<CoalScalar>::max)();
p1 = p2 = normal =
Vec3s::Constant(std::numeric_limits<CoalScalar>::quiet_NaN());
}
};
} // namespace coal
#endif