From 8201402e3495f00e4d98cc0218e7f78523a1c2e2 Mon Sep 17 00:00:00 2001 From: jpan <jpan@253336fb-580f-4252-a368-f3cef5a2a82b> Date: Tue, 19 Jun 2012 08:57:58 +0000 Subject: [PATCH] shape functions git-svn-id: https://kforge.ros.org/fcl/fcl_ros@100 253336fb-580f-4252-a368-f3cef5a2a82b --- trunk/fcl/CMakeLists.txt | 2 +- .../include/fcl/geometric_shapes_intersect.h | 9 + .../include/fcl/geometric_shapes_utility.h | 2 +- trunk/fcl/include/fcl/gjk.h | 325 ++++++++ trunk/fcl/include/fcl/transform.h | 12 + trunk/fcl/include/fcl/vec_3f.h | 2 +- trunk/fcl/src/geometric_shapes_intersect.cpp | 62 ++ trunk/fcl/src/gjk.cpp | 728 ++++++++++++++++++ 8 files changed, 1139 insertions(+), 3 deletions(-) create mode 100644 trunk/fcl/include/fcl/gjk.h create mode 100644 trunk/fcl/src/gjk.cpp diff --git a/trunk/fcl/CMakeLists.txt b/trunk/fcl/CMakeLists.txt index 9f334478..3cdce3a6 100644 --- a/trunk/fcl/CMakeLists.txt +++ b/trunk/fcl/CMakeLists.txt @@ -37,7 +37,7 @@ link_directories(${CCD_LIBRARY_DIRS}) add_definitions(-DUSE_SVMLIGHT=0) -add_library(${PROJECT_NAME} SHARED src/BV/AABB.cpp src/BV/OBB.cpp src/BV/RSS.cpp src/BV/kIOS.cpp src/traversal_node_base.cpp src/traversal_node_bvhs.cpp src/intersect.cpp src/motion.cpp src/BV_fitter.cpp src/BV_splitter.cpp src/BVH_model.cpp src/BVH_utility.cpp src/transform.cpp src/simple_setup.cpp src/geometric_shapes.cpp src/geometric_shapes_utility.cpp src/geometric_shapes_intersect.cpp src/collision_node.cpp src/traversal_recurse.cpp src/broad_phase_collision.cpp src/collision.cpp src/collision_func_matrix.cpp src/interval_tree.cpp src/conservative_advancement.cpp src/matrix_3f.cpp src/interval.cpp src/interval_vector.cpp src/interval_matrix.cpp src/taylor_model.cpp src/taylor_vector.cpp src/taylor_matrix.cpp src/distance_func_matrix.cpp src/distance.cpp) +add_library(${PROJECT_NAME} SHARED src/BV/AABB.cpp src/BV/OBB.cpp src/BV/RSS.cpp src/BV/kIOS.cpp src/traversal_node_base.cpp src/traversal_node_bvhs.cpp src/intersect.cpp src/motion.cpp src/BV_fitter.cpp src/BV_splitter.cpp src/BVH_model.cpp src/BVH_utility.cpp src/transform.cpp src/simple_setup.cpp src/geometric_shapes.cpp src/geometric_shapes_utility.cpp src/geometric_shapes_intersect.cpp src/collision_node.cpp src/traversal_recurse.cpp src/broad_phase_collision.cpp src/collision.cpp src/collision_func_matrix.cpp src/interval_tree.cpp src/conservative_advancement.cpp src/matrix_3f.cpp src/interval.cpp src/interval_vector.cpp src/interval_matrix.cpp src/taylor_model.cpp src/taylor_vector.cpp src/taylor_matrix.cpp src/distance_func_matrix.cpp src/distance.cpp src/gjk.cpp) target_link_libraries(${PROJECT_NAME} ${FLANN_LIBRARIES} ${CCD_LIBRARIES}) diff --git a/trunk/fcl/include/fcl/geometric_shapes_intersect.h b/trunk/fcl/include/fcl/geometric_shapes_intersect.h index 30d49f0c..b09d1bc2 100644 --- a/trunk/fcl/include/fcl/geometric_shapes_intersect.h +++ b/trunk/fcl/include/fcl/geometric_shapes_intersect.h @@ -205,6 +205,15 @@ bool shapeTriangleIntersect(const S& s, const SimpleTransform& tf, triDeleteGJKObject(o2); } + +/** \brief distance computation between two shapes */ +template<typename S1, typename S2> +BVH_REAL shapeDistance(const S1& s1, const SimpleTransform& tf1, + const S2& s2, const SimpleTransform& tf2) +{ + return 0.0; +} + } #endif diff --git a/trunk/fcl/include/fcl/geometric_shapes_utility.h b/trunk/fcl/include/fcl/geometric_shapes_utility.h index aa86a102..208a3b6d 100644 --- a/trunk/fcl/include/fcl/geometric_shapes_utility.h +++ b/trunk/fcl/include/fcl/geometric_shapes_utility.h @@ -82,7 +82,7 @@ template<> void computeBV<AABB, Convex>(const Convex& s, const SimpleTransform& tf, AABB& bv); -/** the bounding volume for half space back of plane for OBB, it is the plane itself */ +/** \brief the bounding volume for half space back of plane for OBB, it is the plane itself */ template<> void computeBV<AABB, Plane>(const Plane& s, const SimpleTransform& tf, AABB& bv); diff --git a/trunk/fcl/include/fcl/gjk.h b/trunk/fcl/include/fcl/gjk.h new file mode 100644 index 00000000..6e50c03f --- /dev/null +++ b/trunk/fcl/include/fcl/gjk.h @@ -0,0 +1,325 @@ +/* + * Software License Agreement (BSD License) + * + * Copyright (c) 2011, Willow Garage, Inc. + * 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 Willow Garage, Inc. 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. + */ + +/** \author Jia Pan */ + +#ifndef FCL_GJK_H +#define FCL_GJK_H + +#include "fcl/geometric_shapes.h" +#include "fcl/matrix_3f.h" +#include "fcl/transform.h" + +namespace fcl +{ + + +Vec3f getSupport(const ShapeBase* shape, const Vec3f& dir); + +struct MinkowskiDiff +{ + const ShapeBase* shapes[2]; + Matrix3f toshape1; + SimpleTransform toshape0; + + MinkowskiDiff() { } + + inline Vec3f support0(const Vec3f& d) const + { + return getSupport(shapes[0], d); + } + + inline Vec3f support1(const Vec3f& d) const + { + return toshape0.transform(getSupport(shapes[1], toshape1 * d)); + } + + inline Vec3f support(const Vec3f& d) const + { + return support0(d) - support1(-d); + } + + inline Vec3f support(const Vec3f& d, size_t index) const + { + if(index) + return support1(d); + else + return support0(d); + } + +}; + +namespace details +{ + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, BVH_REAL* w, size_t& m); + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, const Vec3f& c, BVH_REAL* w, size_t& m); + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Vec3f& d, BVH_REAL* w, size_t& m); + +} + +static const BVH_REAL GJK_EPS = 0.0001; +static const size_t GJK_MAX_ITERATIONS = 128; + +struct GJK +{ + struct SimplexV + { + Vec3f d; // support direction + Vec3f w; // support vector + }; + + struct Simplex + { + SimplexV* c[4]; // simplex vertex + BVH_REAL p[4]; // weight + size_t rank; // size of simplex (number of vertices) + }; + + enum Status {Valid, Inside, Failed}; + + MinkowskiDiff shape; + Vec3f ray; + BVH_REAL distance; + Simplex simplices[2]; + + + GJK() { initialize(); } + + void initialize() + { + ray = Vec3f(); + nfree = 0; + status = Failed; + current = 0; + distance = 0.0; + } + + Status evaluate(const MinkowskiDiff& shape_, const Vec3f& guess); + + void getSupport(const Vec3f& d, SimplexV& sv) const; + + void removeVertex(Simplex& simplex); + + void appendVertex(Simplex& simplex, const Vec3f& v); + + bool encloseOrigin(); + + inline Simplex* getSimplex() const + { + return simplex; + } + +private: + SimplexV store_v[4]; + SimplexV* free_v[4]; + size_t nfree; + size_t current; + Simplex* simplex; + Status status; +}; + + +static const size_t EPA_MAX_FACES = 128; +static const size_t EPA_MAX_VERTICES = 64; +static const BVH_REAL EPA_EPS = 0.0001; +static const size_t EPA_MAX_ITERATIONS = 255; + +struct EPA +{ + typedef GJK::SimplexV SimplexV; + struct SimplexF + { + Vec3f n; + BVH_REAL d; + SimplexV* c[3]; // a face has three vertices + SimplexF* f[3]; // a face has three adjacent faces + SimplexF* l[2]; // the pre and post faces in the list + size_t e[3]; + size_t pass; + }; + + struct SimplexList + { + SimplexF* root; + size_t count; + SimplexList() : root(NULL), count(0) {} + void append(SimplexF* face) + { + face->l[0] = NULL; + face->l[1] = root; + if(root) root->l[0] = face; + root = face; + ++count; + } + + void remove(SimplexF* face) + { + if(face->l[1]) face->l[1]->l[0] = face->l[0]; + if(face->l[0]) face->l[0]->l[1] = face->l[1]; + if(face == root) root = face->l[1]; + --count; + } + }; + + static inline void bind(SimplexF* fa, size_t ea, SimplexF* fb, size_t eb) + { + fa->e[ea] = eb; fa->f[ea] = fb; + fb->e[eb] = ea; fb->f[eb] = fa; + } + + struct SimplexHorizon + { + SimplexF* cf; // current face in the horizon + SimplexF* ff; // first face in the horizon + size_t nf; // number of faces in the horizon + SimplexHorizon() : cf(NULL), ff(NULL), nf(0) {} + }; + + enum Status {Valid, Touching, Degenerated, NonConvex, InvalidHull, OutOfFaces, OutOfVertices, AccuracyReached, FallBack, Failed}; + + Status status; + GJK::Simplex result; + Vec3f normal; + BVH_REAL depth; + SimplexV sv_store[EPA_MAX_VERTICES]; + SimplexF fc_store[EPA_MAX_FACES]; + size_t nextsv; + SimplexList hull, stock; + + EPA() + { + initialize(); + } + + void initialize() + { + status = Failed; + normal = Vec3f(0, 0, 0); + depth = 0; + nextsv = 0; + for(size_t i = 0; i < EPA_MAX_FACES; ++i) + stock.append(&fc_store[EPA_MAX_FACES-i-1]); + } + + bool getEdgeDist(SimplexF* face, SimplexV* a, SimplexV* b, BVH_REAL& dist); + + SimplexF* newFace(SimplexV* a, SimplexV* b, SimplexV* c, bool forced); + + /** \brief Find the best polytope face to split */ + SimplexF* findBest(); + + Status evaluate(GJK& gjk, const Vec3f& guess); + + /** \brief the goal is to add a face connecting vertex w and face edge f[e] */ + bool expand(size_t pass, SimplexV* w, SimplexF* f, size_t e, SimplexHorizon& horizon); +}; + +template<typename S1, typename S2> +bool shapeDistance2(const S1& s1, const SimpleTransform& tf1, + const S2& s2, const SimpleTransform& tf2, + BVH_REAL& distance) +{ + Vec3f guess(1, 0, 0); + MinkowskiDiff shape; + shape.shapes[0] = &s1; + shape.shapes[1] = &s2; + shape.toshape1 = tf2.getRotation().transposeTimes(tf1.getRotation()); + shape.toshape0 = tf1.inverseTimes(tf2); + + GJK gjk; + GJK::Status gjk_status = gjk.evaluate(shape, -guess); + if(gjk_status == GJK::Valid) + { + Vec3f w0, w1; + for(size_t i = 0; i < gjk.getSimplex()->rank; ++i) + { + BVH_REAL p = gjk.getSimplex()->p[i]; + w0 += shape.support(gjk.getSimplex()->c[i]->d, 0) * p; + w1 += shape.support(-gjk.getSimplex()->c[i]->d, 1) * p; + } + + distance = (w0 - w1).length(); + return true; + } + else + return false; +} + +template<typename S1, typename S2> +bool shapeIntersect(const S1& s1, const SimpleTransform& tf1, + const S2& s2, const SimpleTransform& tf2, + Vec3f* contact_points = NULL, BVH_REAL* penetration_depth = NULL, Vec3f* normal = NULL) +{ + Vec3f guess(1, 0, 0); + MinkowskiDiff shape; + shape.shapes[0] = &s1; + shape.shapes[1] = &s2; + shape.toshape1 = tf2.getRotation().transposeTimes(tf1.getRotation()); + shape.toshape0 = tf1.inverseTimes(tf2); + + GJK gjk; + GJK::Status gjk_status = gjk.evaluate(shape, -guess); + switch(gjk_status) + { + case GJK::Inside: + { + EPA epa; + EPA::Status epa_status = epa.evaluate(gjk, -guess); + if(epa_status != EPA::Failed) + { + Vec3f w0; + for(size_t i = 0; i < epa.result.rank; ++i) + { + w0 += shape.support(epa.result.c[i]->d, 0) * epa.result.p[i]; + } + if(penetration_depth) *penetration_depth = -epa.depth; + if(normal) *normal = -epa.normal; + if(contact_points) *contact_points = tf1.transform(w0 - epa.normal*(epa.depth *0.5)); + return true; + } + else return false; + } + break; + } + + return false; +} + +} + + +#endif diff --git a/trunk/fcl/include/fcl/transform.h b/trunk/fcl/include/fcl/transform.h index 0fb56cf8..f72038ff 100644 --- a/trunk/fcl/include/fcl/transform.h +++ b/trunk/fcl/include/fcl/transform.h @@ -221,6 +221,18 @@ public: return q.transform(v) + T; } + SimpleTransform inverse() const + { + Matrix3f Rinv = R.transpose(); + return SimpleTransform(Rinv, Rinv * (-T)); + } + + SimpleTransform inverseTimes(const SimpleTransform& other) const + { + Vec3f v = other.T - T; + return SimpleTransform(R.transposeTimes(other.R), R.transposeTimes(v)); + } + const SimpleTransform& operator *= (const SimpleTransform& other) { T = q.transform(other.T) + T; diff --git a/trunk/fcl/include/fcl/vec_3f.h b/trunk/fcl/include/fcl/vec_3f.h index 7360e5f9..002f6104 100644 --- a/trunk/fcl/include/fcl/vec_3f.h +++ b/trunk/fcl/include/fcl/vec_3f.h @@ -78,7 +78,7 @@ public: inline Vec3fX& operator -= (U t) { data -= t; return *this; } inline Vec3fX& operator *= (U t) { data *= t; return *this; } inline Vec3fX& operator /= (U t) { data /= t; return *this; } - inline Vec3fX operator - () { return Vec3fX(-data); } + inline Vec3fX operator - () const { return Vec3fX(-data); } inline Vec3fX cross(const Vec3fX& other) const { return Vec3fX(details::cross_prod(data, other.data)); } inline U dot(const Vec3fX& other) const { return details::dot_prod3(data, other.data); } inline bool normalize() diff --git a/trunk/fcl/src/geometric_shapes_intersect.cpp b/trunk/fcl/src/geometric_shapes_intersect.cpp index ea139967..2e56b4de 100644 --- a/trunk/fcl/src/geometric_shapes_intersect.cpp +++ b/trunk/fcl/src/geometric_shapes_intersect.cpp @@ -82,6 +82,68 @@ struct ccd_triangle_t : public ccd_obj_t ccd_vec3_t c; }; +/* +static int __ccdGJK(const void *obj1, const void *obj2, + const ccd_t *ccd, ccd_simplex_t *simplex) +{ + unsigned long iterations; + ccd_vec3_t dir; // direction vector + ccd_support_t a, b, c; + int do_simplex_res; + + // initialize simplex struct + ccdSimplexInit(simplex); + + ccd->first_dir(obj1, obj2, &dir); + __ccdSupport(obj1, obj2, &dir, ccd, &a); + ccdSimplexAdd(simplex, &a); + + // set up direction vector to as (O - last) which is exactly -last + ccdVec3Copy(&dir, &a.v); + ccdVec3Scale(&dir, -CCD_ONE); + __ccdSupport(obj1, obj2, &dir, ccd, &b); + ccdSimplexAdd(simplex, &b); + + // start iterations + for(iterations = 0UL; iterations < ccd->max_iterations; ++iterations) + { + ccd_vec3_t ba, bo, tmp; + ccdVec3Sub2(&ba, &a.v, &b.v); + ccdVec3Copy(&bo, &b.v); + ccdVec3Scale(&bo, -CCD_ONE); + + ccd_real_t dot = ccdVec3Dot(&ba, &bo); + ccdVec3Cross(&tmp, &ba, &bo); + if(ccdIsZero(dot) || dot < CCD_ZERO) + { + ccdSimplexSet(simplex, 0, &b); + ccdSimpleSetSize(simplex, 1); + ccdVec3Copy(&dir, &bo); + } + else + { + tripleCross(&ba, &bo, &ba, &dir); + } + __ccdSupport(obj1, obj2, &dir, ccd, &c); + ccdSimplexAdd(simplex, &c); + + if(ccdSimplexSize(simplex) == 2) + { + + } + else if(ccdSimplexSize(simplex) == 3) + { + + } + + + + } +} + +*/ + + /** Basic shape to ccd shape */ static void shapeToGJK(const ShapeBase& s, const SimpleTransform& tf, ccd_obj_t* o) { diff --git a/trunk/fcl/src/gjk.cpp b/trunk/fcl/src/gjk.cpp new file mode 100644 index 00000000..8f497023 --- /dev/null +++ b/trunk/fcl/src/gjk.cpp @@ -0,0 +1,728 @@ +/* + * Software License Agreement (BSD License) + * + * Copyright (c) 2011, Willow Garage, Inc. + * 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 Willow Garage, Inc. 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. + */ + +/** \author Jia Pan */ + +#include "fcl/gjk.h" + +namespace fcl +{ + + + +Vec3f getSupport(const ShapeBase* shape, const Vec3f& dir) +{ + switch(shape->getObjectType()) + { + case GEOM_BOX: + { + const Box* box = static_cast<const Box*>(shape); + return Vec3f((dir[0]>0)?(box->side[0]/2):(-box->side[0]/2), + (dir[1]>0)?(box->side[1]/2):(-box->side[1]/2), + (dir[2]>0)?(box->side[2]/2):(-box->side[2]/2)); + } + break; + case GEOM_SPHERE: + { + const Sphere* sphere = static_cast<const Sphere*>(shape); + return dir * sphere->radius; + } + break; + case GEOM_CAPSULE: + { + const Capsule* capsule = static_cast<const Capsule*>(shape); + BVH_REAL half_h = capsule->lz * 0.5; + Vec3f pos1(0, 0, half_h); + Vec3f pos2(0, 0, -half_h); + Vec3f v = dir * capsule->radius; + pos1 += v; + pos2 += v; + if(dir.dot(pos1) > dir.dot(pos2)) + return pos1; + else return pos2; + } + break; + case GEOM_CONE: + { + const Cone* cone = static_cast<const Cone*>(shape); + BVH_REAL zdist = dir[0] * dir[0] + dir[1] * dir[1]; + BVH_REAL len = zdist + dir[2] * dir[2]; + zdist = std::sqrt(zdist); + len = std::sqrt(len); + BVH_REAL half_h = cone->lz * 0.5; + BVH_REAL radius = cone->radius; + + BVH_REAL sin_a = radius / std::sqrt(radius * radius + 4 * half_h * half_h); + + if(dir[2] > len * sin_a) + return Vec3f(0, 0, half_h); + else if(zdist > 0) + { + BVH_REAL rad = radius / zdist; + return Vec3f(rad * dir[0], rad * dir[1], -half_h); + } + else + return Vec3f(0, 0, -half_h); + } + break; + case GEOM_CYLINDER: + { + const Cylinder* cylinder = static_cast<const Cylinder*>(shape); + BVH_REAL zdist = std::sqrt(dir[0] * dir[0] + dir[1] * dir[1]); + BVH_REAL half_h = cylinder->lz * 0.5; + if(zdist == 0.0) + { + return Vec3f(0, 0, (dir[2]>0)? half_h:-half_h); + } + else + { + BVH_REAL d = cylinder->radius / zdist; + return Vec3f(d * dir[0], d * dir[1], (dir[2]>0)?half_h:-half_h); + } + } + break; + case GEOM_CONVEX: + { + const Convex* convex = static_cast<const Convex*>(shape); + const Vec3f& center = convex->center; + BVH_REAL maxdot = - std::numeric_limits<BVH_REAL>::max(); + Vec3f* curp = convex->points; + Vec3f bestv; + for(size_t i = 0; i < convex->num_points; ++i, curp+=1) + { + Vec3f p = (*curp) - center; + BVH_REAL dot = dir.dot(p); + if(dot > maxdot) + { + bestv = *curp; + maxdot = dot; + } + } + return bestv; + } + break; + case GEOM_PLANE: + { + return Vec3f(0, 0, 0); + } + break; + } + + return Vec3f(0, 0, 0); +} + + +namespace details +{ + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, BVH_REAL* w, size_t& m) +{ + const Vec3f d = b - a; + const BVH_REAL l = d.sqrLength(); + if(l > 0) + { + const BVH_REAL t(l > 0 ? - a.dot(d) / l : 0); + if(t >= 1) { w[0] = 0; w[1] = 1; m = 2; return b.sqrLength(); } // m = 0x10 + else if(t <= 0) { w[0] = 1; w[1] = 0; m = 1; return a.sqrLength(); } // m = 0x01 + else { w[0] = 1 - (w[1] = t); m = 3; return (a + d * t).sqrLength(); } // m = 0x11 + } + + return -1; +} + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, const Vec3f& c, BVH_REAL* w, size_t& m) +{ + static const size_t nexti[3] = {1, 2, 0}; + const Vec3f* vt[] = {&a, &b, &c}; + const Vec3f dl[] = {a - b, b - c, c - a}; + const Vec3f& n = dl[0].cross(dl[1]); + const BVH_REAL l = n.sqrLength(); + + if(l > 0) + { + BVH_REAL mindist = -1; + BVH_REAL subw[2] = {0, 0}; + size_t subm = 0; + for(size_t i = 0; i < 3; ++i) + { + if(vt[i]->dot(dl[i].cross(n)) > 0) // origin is to the outside part of the triangle edge, then the optimal can only be on the edge + { + size_t j = nexti[i]; + BVH_REAL subd = projectOrigin(*vt[i], *vt[j], subw, subm); + if(mindist < 0 || subd < mindist) + { + mindist = subd; + m = static_cast<size_t>(((subm&1)?1<<i:0) + ((subm&2)?1<<j:0)); + w[i] = subw[0]; + w[j] = subw[1]; + w[nexti[j]] = 0; + } + } + } + + if(mindist < 0) // the origin project is within the triangle + { + BVH_REAL d = a.dot(n); + BVH_REAL s = sqrt(l); + Vec3f p = n * (d / l); + mindist = p.sqrLength(); + m = 7; // m = 0x111 + w[0] = dl[1].cross(b-p).length() / s; + w[1] = dl[2].cross(c-p).length() / s; + w[2] = 1 - (w[0] + w[1]); + } + + return mindist; + } + return -1; +} + +BVH_REAL projectOrigin(const Vec3f& a, const Vec3f& b, const Vec3f& c, const Vec3f& d, BVH_REAL* w, size_t& m) +{ + static const size_t nexti[] = {1, 2, 0}; + const Vec3f* vt[] = {&a, &b, &c, &d}; + const Vec3f dl[3] = {a-d, b-d, c-d}; + BVH_REAL vl = triple(dl[0], dl[1], dl[2]); + bool ng = (vl * a.dot((b-c).cross(a-b))) <= 0; + if(ng && std::abs(vl) > 0) // abs(vl) == 0, the tetrahedron is degenerated; if ng is false, then the last vertex in the tetrahedron does not grow toward the origin (in fact origin is on the other side of the abc face) + { + BVH_REAL mindist = -1; + BVH_REAL subw[3] = {0, 0, 0}; + size_t subm = 0; + for(size_t i = 0; i < 3; ++i) + { + size_t j = nexti[i]; + BVH_REAL s = vl * d.dot(dl[i].cross(dl[j])); + if(s > 0) // the origin is to the outside part of a triangle face, then the optimal can only be on the triangle face + { + BVH_REAL subd = projectOrigin(*vt[i], *vt[j], d, subw, subm); + if(mindist < 0 || subd < mindist) + { + mindist = subd; + m = static_cast<size_t>( (subm&1?1<<i:0) + (subm&2?1<<j:0) + (subm&4?8:0) ); + w[i] = subw[0]; + w[j] = subw[1]; + w[nexti[j]] = 0; + w[3] = subw[2]; + } + } + } + + if(mindist < 0) + { + mindist = 0; + m = 15; + w[0] = triple(c, b, d) / vl; + w[1] = triple(a, c, d) / vl; + w[2] = triple(b, a, d) / vl; + w[3] = 1 - (w[0] + w[1] + w[2]); + } + + return mindist; + } + return -1; +} + + +} // detail + + + +GJK::Status GJK::evaluate(const MinkowskiDiff& shape_, const Vec3f& guess) +{ + size_t iterations = 0; + BVH_REAL sqdist = 0; + BVH_REAL alpha = 0; + Vec3f lastw[4]; + size_t clastw = 0; + + free_v[0] = &store_v[0]; + free_v[1] = &store_v[1]; + free_v[2] = &store_v[2]; + free_v[3] = &store_v[3]; + + nfree = 4; + current = 0; + status = Valid; + shape = shape_; + distance = 0.0; + simplices[0].rank = 0; + ray = guess; + + BVH_REAL sqrl = ray.sqrLength(); + appendVertex(simplices[0], (sqrl>0) ? -ray : Vec3f(1, 0, 0)); + simplices[0].p[0] = 1; + ray = simplices[0].c[0]->w; + sqdist = sqrl; + lastw[0] = lastw[1] = lastw[2] = lastw[3] = ray; + + do + { + size_t next = 1 - current; + Simplex& curr_simplex = simplices[current]; + Simplex& next_simplex = simplices[next]; + + BVH_REAL rl = ray.sqrLength(); + if(rl < GJK_EPS) // means origin is near the face of original simplex, return touch + { + status = Inside; + break; + } + + appendVertex(curr_simplex, -ray); // see below, ray points away from origin + + Vec3f& w = curr_simplex.c[curr_simplex.rank - 1]->w; + bool found = false; + for(size_t i = 0; i < 4; ++i) + { + if((w - lastw[i]).sqrLength() < GJK_EPS) + { + found = true; break; + } + } + + if(found) + { + removeVertex(simplices[current]); + break; + } + else + { + lastw[clastw = (clastw+1)&3] = w; + } + + // check for termination, from bullet + BVH_REAL omega = ray.dot(w) / rl; + alpha = std::max(alpha, omega); + if((rl - alpha) - GJK_EPS * rl <= 0) + { + removeVertex(simplices[current]); + break; + } + + // reduce simplex and decide the extend direction + BVH_REAL weights[4]; + size_t mask = 0; // decide the simplex vertices that compose the minimal distance + switch(curr_simplex.rank) + { + case 2: + sqdist = details::projectOrigin(curr_simplex.c[0]->w, curr_simplex.c[1]->w, weights, mask); break; + case 3: + sqdist = details::projectOrigin(curr_simplex.c[0]->w, curr_simplex.c[1]->w, curr_simplex.c[2]->w, weights, mask); break; + case 4: + sqdist = details::projectOrigin(curr_simplex.c[0]->w, curr_simplex.c[1]->w, curr_simplex.c[2]->w, curr_simplex.c[3]->w, weights, mask); break; + } + + if(sqdist >= 0) + { + next_simplex.rank = 0; + ray = Vec3f(); + current = next; + for(size_t i = 0; i < curr_simplex.rank; ++i) + { + if(mask & (1 << i)) + { + next_simplex.c[next_simplex.rank] = curr_simplex.c[i]; + next_simplex.p[next_simplex.rank++] = weights[i]; + ray += curr_simplex.c[i]->w * weights[i]; + } + else + free_v[nfree++] = curr_simplex.c[i]; + } + if(mask == 15) status = Inside; // the origin is within the 4-simplex, collision + } + else + { + removeVertex(simplices[current]); + break; + } + + status = ((++iterations) < GJK_MAX_ITERATIONS) ? status : Failed; + + } while(status == Valid); + + simplex = &simplices[current]; + switch(status) + { + case Valid: distance = ray.length(); break; + case Inside: distance = 0; break; + } + return status; +} + +void GJK::getSupport(const Vec3f& d, SimplexV& sv) const +{ + sv.d = d.normalized(); + sv.w = shape.support(sv.d); +} + +void GJK::removeVertex(Simplex& simplex) +{ + free_v[nfree++] = simplex.c[--simplex.rank]; +} + +void GJK::appendVertex(Simplex& simplex, const Vec3f& v) +{ + simplex.p[simplex.rank] = 0; // initial weight 0 + simplex.c[simplex.rank] = free_v[--nfree]; // set the memory + getSupport(v, *simplex.c[simplex.rank++]); +} + +bool GJK::encloseOrigin() +{ + switch(simplex->rank) + { + case 1: + { + for(size_t i = 0; i < 3; ++i) + { + Vec3f axis; + axis[i] = 1; + appendVertex(*simplex, axis); + if(encloseOrigin()) return true; + removeVertex(*simplex); + appendVertex(*simplex, -axis); + if(encloseOrigin()) return true; + removeVertex(*simplex); + } + } + break; + case 2: + { + Vec3f d = simplex->c[1]->w - simplex->c[0]->w; + for(size_t i = 0; i < 3; ++i) + { + Vec3f axis; + axis[i] = 1; + Vec3f p = d.cross(axis); + if(p.sqrLength() > 0) + { + appendVertex(*simplex, p); + if(encloseOrigin()) return true; + removeVertex(*simplex); + appendVertex(*simplex, -p); + if(encloseOrigin()) return true; + removeVertex(*simplex); + } + } + } + break; + case 3: + { + Vec3f n = (simplex->c[1]->w - simplex->c[0]->w).cross(simplex->c[2]->w - simplex->c[0]->w); + if(n.sqrLength() > 0) + { + appendVertex(*simplex, n); + if(encloseOrigin()) return true; + removeVertex(*simplex); + appendVertex(*simplex, -n); + if(encloseOrigin()) return true; + removeVertex(*simplex); + } + } + break; + case 4: + { + if(std::abs(triple(simplex->c[0]->w - simplex->c[3]->w, simplex->c[1]->w - simplex->c[3]->w, simplex->c[2]->w - simplex->c[3]->w)) > 0) + return true; + } + break; + } + + return false; +} + + +bool EPA::getEdgeDist(SimplexF* face, SimplexV* a, SimplexV* b, BVH_REAL& dist) +{ + Vec3f ba = b->w - a->w; + Vec3f n_ab = ba.cross(face->n); + BVH_REAL a_dot_nab = a->w.dot(n_ab); + + if(a_dot_nab < 0) // the origin is on the outside part of ab + { + BVH_REAL ba_l2 = ba.sqrLength(); + + // following is similar to projectOrigin for two points + // however, as we dont need to compute the parameterization, dont need to compute 0 or 1 + BVH_REAL a_dot_ba = a->w.dot(ba); + BVH_REAL b_dot_ba = b->w.dot(ba); + + if(a_dot_ba > 0) + dist = a->w.length(); + else if(b_dot_ba < 0) + dist = b->w.length(); + else + { + BVH_REAL a_dot_b = a->w.dot(b->w); + dist = std::sqrt(std::max(a->w.sqrLength() * b->w.sqrLength() - a_dot_b * a_dot_b, (BVH_REAL)0)); + } + + return true; + } + + return false; +} + +EPA::SimplexF* EPA::newFace(SimplexV* a, SimplexV* b, SimplexV* c, bool forced) +{ + if(stock.root) + { + SimplexF* face = stock.root; + stock.remove(face); + hull.append(face); + face->pass = 0; + face->c[0] = a; + face->c[1] = b; + face->c[2] = c; + face->n = (b->w - a->w).cross(c->w - a->w); + BVH_REAL l = face->n.length(); + + if(l > EPA_EPS) + { + if(!(getEdgeDist(face, a, b, face->d) || + getEdgeDist(face, b, c, face->d) || + getEdgeDist(face, c, a, face->d))) + { + face->d = a->w.dot(face->n) / l; + } + + face->n /= l; + if(forced || face->d >= -EPA_EPS) + return face; + else + status = NonConvex; + } + else + status = Degenerated; + + hull.remove(face); + stock.append(face); + return NULL; + } + + status = stock.root ? OutOfVertices : OutOfFaces; + return NULL; +} + +/** \brief Find the best polytope face to split */ +EPA::SimplexF* EPA::findBest() +{ + SimplexF* minf = hull.root; + BVH_REAL mind = minf->d * minf->d; + for(SimplexF* f = minf->l[1]; f; f = f->l[1]) + { + BVH_REAL sqd = f->d * f->d; + if(sqd < mind) + { + minf = f; + mind = sqd; + } + } + return minf; +} + +EPA::Status EPA::evaluate(GJK& gjk, const Vec3f& guess) +{ + GJK::Simplex& simplex = *gjk.getSimplex(); + if((simplex.rank > 1) && gjk.encloseOrigin()) + { + while(hull.root) + { + SimplexF* f = hull.root; + hull.remove(f); + stock.append(f); + } + + status = Valid; + nextsv = 0; + + if((simplex.c[0]->w - simplex.c[3]->w).dot((simplex.c[1]->w - simplex.c[3]->w).cross(simplex.c[2]->w - simplex.c[3]->w)) < 0) + { + SimplexV* tmp = simplex.c[0]; + simplex.c[0] = simplex.c[1]; + simplex.c[1] = tmp; + + BVH_REAL tmpv = simplex.p[0]; + simplex.p[0] = simplex.p[1]; + simplex.p[1] = tmpv; + } + + SimplexF* tetrahedron[] = {newFace(simplex.c[0], simplex.c[1], simplex.c[2], true), + newFace(simplex.c[1], simplex.c[0], simplex.c[3], true), + newFace(simplex.c[2], simplex.c[1], simplex.c[3], true), + newFace(simplex.c[0], simplex.c[2], simplex.c[3], true) }; + + if(hull.count == 4) + { + SimplexF* best = findBest(); // find the best face (the face with the minimum distance to origin) to split + SimplexF outer = *best; + size_t pass = 0; + size_t iterations = 0; + + // set the face connectivity + bind(tetrahedron[0], 0, tetrahedron[1], 0); + bind(tetrahedron[0], 1, tetrahedron[2], 0); + bind(tetrahedron[0], 2, tetrahedron[3], 0); + bind(tetrahedron[1], 1, tetrahedron[3], 2); + bind(tetrahedron[1], 2, tetrahedron[2], 1); + bind(tetrahedron[2], 2, tetrahedron[3], 1); + + status = Valid; + for(; iterations < EPA_MAX_ITERATIONS; ++iterations) + { + if(nextsv < EPA_MAX_VERTICES) + { + SimplexHorizon horizon; + SimplexV* w = &sv_store[nextsv++]; + bool valid = true; + best->pass = ++pass; + gjk.getSupport(best->n, *w); + BVH_REAL wdist = best->n.dot(w->w) - best->d; + if(wdist > EPA_EPS) + { + for(size_t j = 0; (j < 3) && valid; ++j) + { + valid &= expand(pass, w, best->f[j], best->e[j], horizon); + } + + + if(valid && horizon.nf >= 3) + { + // need to add the edge connectivity between first and last faces + bind(horizon.ff, 2, horizon.cf, 1); + hull.remove(best); + stock.append(best); + best = findBest(); + outer = *best; + } + else + { + status = InvalidHull; break; + } + } + else + { + status = AccuracyReached; break; + } + } + else + { + status = OutOfVertices; break; + } + } + + Vec3f projection = outer.n * outer.d; + normal = outer.n; + depth = outer.d; + result.rank = 3; + result.c[0] = outer.c[0]; + result.c[1] = outer.c[1]; + result.c[2] = outer.c[2]; + result.p[0] = ((outer.c[1]->w - projection).cross(outer.c[2]->w - projection)).length(); + result.p[1] = ((outer.c[2]->w - projection).cross(outer.c[0]->w - projection)).length(); + result.p[2] = ((outer.c[0]->w - projection).cross(outer.c[1]->w - projection)).length(); + + BVH_REAL sum = result.p[0] + result.p[1] + result.p[2]; + result.p[0] /= sum; + result.p[1] /= sum; + result.p[2] /= sum; + return status; + } + } + + status = FallBack; + normal = -guess; + BVH_REAL nl = normal.length(); + if(nl > 0) normal /= nl; + else normal = Vec3f(1, 0, 0); + depth = 0; + result.rank = 1; + result.c[0] = simplex.c[0]; + result.p[0] = 1; + return status; +} + + +/** \brief the goal is to add a face connecting vertex w and face edge f[e] */ +bool EPA::expand(size_t pass, SimplexV* w, SimplexF* f, size_t e, SimplexHorizon& horizon) +{ + static const size_t nexti[] = {1, 2, 0}; + static const size_t previ[] = {2, 0, 1}; + + if(f->pass != pass) + { + const size_t e1 = nexti[e]; + + // case 1: the new face is not degenerated, i.e., the new face is not coplanar with the old face f. + if(f->n.dot(w->w) - f->d < -EPA_EPS) + { + SimplexF* nf = newFace(f->c[e1], f->c[e], w, false); + if(nf) + { + // add face-face connectivity + bind(nf, 0, f, e); + + // if there is last face in the horizon, then need to add another connectivity, i.e. the edge connecting the current new add edge and the last new add edge. + // This does not finish all the connectivities because the final face need to connect with the first face, this will be handled in the evaluate function. + // Notice the face is anti-clockwise, so the edges are 0 (bottom), 1 (right), 2 (left) + if(horizon.cf) + bind(nf, 2, horizon.cf, 1); + else + horizon.ff = nf; + + horizon.cf = nf; + ++horizon.nf; + return true; + } + } + else // case 2: the new face is coplanar with the old face f. We need to add two faces and delete the old face + { + const size_t e2 = previ[e]; + f->pass = pass; + if(expand(pass, w, f->f[e1], f->e[e1], horizon) && expand(pass, w, f->f[e2], f->e[e2], horizon)) + { + hull.remove(f); + stock.append(f); + return true; + } + } + } + + return false; +} + + + +} // fcl -- GitLab