Program Listing for File DistanceFieldCollisionDetection.cpp

Return to documentation for file (Simulation/DistanceFieldCollisionDetection.cpp)

#include "DistanceFieldCollisionDetection.h"
#include "Simulation/IDFactory.h"
#include "omp.h"

using namespace PBD;
using namespace Utilities;

int DistanceFieldCollisionDetection::DistanceFieldCollisionBox::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionSphere::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionTorus::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionCylinder::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionHollowBox::TYPE_ID = IDFactory::getId();
int DistanceFieldCollisionDetection::DistanceFieldCollisionObjectWithoutGeometry::TYPE_ID = IDFactory::getId();


DistanceFieldCollisionDetection::DistanceFieldCollisionDetection() :
    CollisionDetection()
{
}

DistanceFieldCollisionDetection::~DistanceFieldCollisionDetection()
{
}

void DistanceFieldCollisionDetection::collisionDetection(SimulationModel &model)
{
    model.resetContacts();
    const SimulationModel::RigidBodyVector &rigidBodies = model.getRigidBodies();
    const SimulationModel::TriangleModelVector &triModels = model.getTriangleModels();
    const SimulationModel::TetModelVector &tetModels = model.getTetModels();
    const ParticleData &pd = model.getParticles();

    std::vector < std::pair<unsigned int, unsigned int>> coPairs;
    for (unsigned int i = 0; i < m_collisionObjects.size(); i++)
    {
        CollisionDetection::CollisionObject *co1 = m_collisionObjects[i];
        for (unsigned int k = 0; k < m_collisionObjects.size(); k++)
        {
            CollisionDetection::CollisionObject *co2 = m_collisionObjects[k];
            if ((i != k))
            {
                // ToDo: self collisions for deformables
                coPairs.push_back({ i, k });
            }
        }
    }

    //omp_set_num_threads(1);
    std::vector<std::vector<ContactData> > contacts_mt;
#ifdef _DEBUG
    const unsigned int maxThreads = 1;
#else
    const unsigned int maxThreads = omp_get_max_threads();
#endif
    contacts_mt.resize(maxThreads);

    #pragma omp parallel default(shared)
    {
        // Update BVHs
        #pragma omp for schedule(static)
        for (int i = 0; i < (int)m_collisionObjects.size(); i++)
        {
            CollisionDetection::CollisionObject *co = m_collisionObjects[i];
            updateAABB(model, co);
            if (isDistanceFieldCollisionObject(co))
            {
                if (co->m_bodyType == CollisionDetection::CollisionObject::TriangleModelCollisionObjectType)
                {
                    DistanceFieldCollisionObject *sco = (DistanceFieldCollisionObject*)co;

                    TriangleModel* tm = triModels[co->m_bodyIndex];
                    const unsigned int offset = tm->getIndexOffset();
                    const IndexedFaceMesh& mesh = tm->getParticleMesh();
                    const unsigned int numVert = mesh.numVertices();
                    sco->m_bvh.init(&pd.getPosition(offset), numVert);
                    sco->m_bvh.update();
                }
                else if (co->m_bodyType == CollisionDetection::CollisionObject::TetModelCollisionObjectType)
                {
                    TetModel* tm = tetModels[co->m_bodyIndex];
                    const unsigned int offset = tm->getIndexOffset();
                    const IndexedTetMesh& mesh = tm->getParticleMesh();
                    const unsigned int numVert = mesh.numVertices();

                    DistanceFieldCollisionObject *sco = (DistanceFieldCollisionObject*)co;
                    sco->m_bvh.init(&pd.getPosition(offset), numVert);
                    sco->m_bvhTets.updateVertices(&pd.getPosition(offset));
                    sco->m_bvhTets0.updateVertices(&pd.getPosition(offset));

                    sco->m_bvh.update();
                    sco->m_bvhTets.update();
                }
            }
        }

        #pragma omp for schedule(static)
        for (int i = 0; i < (int)coPairs.size(); i++)
        {
            std::pair<unsigned int, unsigned int> &coPair = coPairs[i];
            CollisionDetection::CollisionObject *co1 = m_collisionObjects[coPair.first];
            CollisionDetection::CollisionObject *co2 = m_collisionObjects[coPair.second];

            if (((co2->m_bodyType != CollisionDetection::CollisionObject::RigidBodyCollisionObjectType) &&
                (co2->m_bodyType != CollisionDetection::CollisionObject::TetModelCollisionObjectType)) ||
                !isDistanceFieldCollisionObject(co1) ||
                !isDistanceFieldCollisionObject(co2) ||
                !AABB::intersection(co1->m_aabb, co2->m_aabb))
                continue;


            if ((co1->m_bodyType == CollisionDetection::CollisionObject::RigidBodyCollisionObjectType) &&
                (co2->m_bodyType == CollisionDetection::CollisionObject::RigidBodyCollisionObjectType) &&
                ((DistanceFieldCollisionObject*) co1)->m_testMesh)
            {
                RigidBody *rb1 = rigidBodies[co1->m_bodyIndex];
                RigidBody *rb2 = rigidBodies[co2->m_bodyIndex];
                const Real restitutionCoeff = rb1->getRestitutionCoeff() * rb2->getRestitutionCoeff();
                const Real frictionCoeff = rb1->getFrictionCoeff() + rb2->getFrictionCoeff();
                collisionDetectionRigidBodies(rb1, (DistanceFieldCollisionObject*)co1, rb2, (DistanceFieldCollisionObject*)co2,
                    restitutionCoeff, frictionCoeff
                    , contacts_mt
                    );
            }
            else if ((co1->m_bodyType == CollisionDetection::CollisionObject::TriangleModelCollisionObjectType) &&
                    (co2->m_bodyType == CollisionDetection::CollisionObject::RigidBodyCollisionObjectType) &&
                    ((DistanceFieldCollisionObject*)co1)->m_testMesh)
            {
                TriangleModel *tm = triModels[co1->m_bodyIndex];
                RigidBody *rb2 = rigidBodies[co2->m_bodyIndex];
                const unsigned int offset = tm->getIndexOffset();
                const IndexedFaceMesh &mesh = tm->getParticleMesh();
                const unsigned int numVert = mesh.numVertices();
                const Real restitutionCoeff = tm->getRestitutionCoeff() * rb2->getRestitutionCoeff();
                const Real frictionCoeff = tm->getFrictionCoeff() + rb2->getFrictionCoeff();
                collisionDetectionRBSolid(pd, offset, numVert, (DistanceFieldCollisionObject*)co1, rb2, (DistanceFieldCollisionObject*)co2,
                    restitutionCoeff, frictionCoeff
                    , contacts_mt
                    );
            }
            else if ((co1->m_bodyType == CollisionDetection::CollisionObject::TetModelCollisionObjectType) &&
                    (co2->m_bodyType == CollisionDetection::CollisionObject::RigidBodyCollisionObjectType) &&
                    ((DistanceFieldCollisionObject*)co1)->m_testMesh)
            {
                TetModel *tm = tetModels[co1->m_bodyIndex];
                RigidBody *rb2 = rigidBodies[co2->m_bodyIndex];
                const unsigned int offset = tm->getIndexOffset();
                const IndexedTetMesh &mesh = tm->getParticleMesh();
                const unsigned int numVert = mesh.numVertices();
                const Real restitutionCoeff = tm->getRestitutionCoeff() * rb2->getRestitutionCoeff();
                const Real frictionCoeff = tm->getFrictionCoeff() + rb2->getFrictionCoeff();
                collisionDetectionRBSolid(pd, offset, numVert, (DistanceFieldCollisionObject*)co1, rb2, (DistanceFieldCollisionObject*)co2,
                    restitutionCoeff, frictionCoeff
                    , contacts_mt
                    );
            }
            else if ((co1->m_bodyType == CollisionDetection::CollisionObject::TetModelCollisionObjectType) &&
                (co2->m_bodyType == CollisionDetection::CollisionObject::TetModelCollisionObjectType) &&
                ((DistanceFieldCollisionObject*)co1)->m_testMesh)
            {
                TetModel *tm1 = tetModels[co1->m_bodyIndex];
                TetModel *tm2 = tetModels[co2->m_bodyIndex];
                const unsigned int offset = tm1->getIndexOffset();
                const IndexedTetMesh &mesh = tm1->getParticleMesh();
                const unsigned int numVert = mesh.numVertices();
                const Real restitutionCoeff = tm1->getRestitutionCoeff() * tm2->getRestitutionCoeff();
                const Real frictionCoeff = tm1->getFrictionCoeff() + tm2->getFrictionCoeff();
                collisionDetectionSolidSolid(pd, offset, numVert, (DistanceFieldCollisionObject*)co1, tm2, (DistanceFieldCollisionObject*)co2,
                    restitutionCoeff, frictionCoeff
                    , contacts_mt
                );
            }
        }
    }

    for (unsigned int i = 0; i < contacts_mt.size(); i++)
    {
        for (unsigned int j = 0; j < contacts_mt[i].size(); j++)
        {
            if (contacts_mt[i][j].m_type == 1)
                addParticleRigidBodyContact(contacts_mt[i][j].m_index1, contacts_mt[i][j].m_index2,
                    contacts_mt[i][j].m_cp1, contacts_mt[i][j].m_cp2, contacts_mt[i][j].m_normal,
                    contacts_mt[i][j].m_dist, contacts_mt[i][j].m_restitution, contacts_mt[i][j].m_friction);
            else if (contacts_mt[i][j].m_type == 0)
                addRigidBodyContact(contacts_mt[i][j].m_index1, contacts_mt[i][j].m_index2,
                    contacts_mt[i][j].m_cp1, contacts_mt[i][j].m_cp2, contacts_mt[i][j].m_normal,
                    contacts_mt[i][j].m_dist, contacts_mt[i][j].m_restitution, contacts_mt[i][j].m_friction);
            else if (contacts_mt[i][j].m_type == 2)
            {
                addParticleSolidContact(contacts_mt[i][j].m_index1, contacts_mt[i][j].m_index2,
                    contacts_mt[i][j].m_elementIndex2, contacts_mt[i][j].m_bary2,
                    contacts_mt[i][j].m_cp1, contacts_mt[i][j].m_cp2, contacts_mt[i][j].m_normal,
                    contacts_mt[i][j].m_dist, contacts_mt[i][j].m_restitution, contacts_mt[i][j].m_friction);
            }
        }
    }
}

void DistanceFieldCollisionDetection::collisionDetectionRigidBodies(RigidBody *rb1, DistanceFieldCollisionObject *co1, RigidBody *rb2, DistanceFieldCollisionObject *co2,
    const Real restitutionCoeff, const Real frictionCoeff
    , std::vector<std::vector<ContactData> > &contacts_mt
    )
{
    if ((rb1->getMass() == 0.0) && (rb2->getMass() == 0.0))
        return;

    const VertexData &vd = rb1->getGeometry().getVertexData();

    const Vector3r &com2 = rb2->getPosition();

    // remove the rotation of the main axis transformation that is performed
    // to get a diagonal inertia tensor since the distance function is
    // evaluated in local coordinates
    //
    // transformation world to local:
    // p_local = R_initial^T ( R_MAT R^T (p_world - x) - x_initial + x_MAT)
    //
    // transformation local to:
    // p_world = R R_MAT^T (R_initial p_local + x_initial - x_MAT) + x
    //
    const Matrix3r &R = rb2->getTransformationR();
    const Vector3r &v1 = rb2->getTransformationV1();
    const Vector3r &v2 = rb2->getTransformationV2();

    const PointCloudBSH &bvh = ((DistanceFieldCollisionDetection::DistanceFieldCollisionObject*) co1)->m_bvh;
    std::function<bool(unsigned int, unsigned int)> predicate = [&](unsigned int node_index, unsigned int depth)
    {
        const BoundingSphere &bs = bvh.hull(node_index);
        const Vector3r &sphere_x = bs.x();
        const Vector3r sphere_x_w = rb1->getRotation() * sphere_x + rb1->getPosition();

        AlignedBox3r box3f;
        box3f.extend(co2->m_aabb.m_p[0]);
        box3f.extend(co2->m_aabb.m_p[1]);
        const Real dist = box3f.exteriorDistance(sphere_x_w);

        // Test if center of bounding sphere intersects AABB
        if (dist < bs.r())
        {
            // Test if distance of center of bounding sphere to collision object is smaller than the radius
            const Vector3r x = R * (sphere_x_w - com2) + v1;
            const double dist2 = co2->distance(x.template cast<double>(), m_tolerance);
            if (dist2 == std::numeric_limits<double>::max())
                return true;
            if (dist2 < bs.r())
                return true;
        }
        return false;
    };
    std::function<void(unsigned int, unsigned int)> cb = [&](unsigned int node_index, unsigned int depth)
    {
        auto const& node = bvh.node(node_index);
        if (!node.is_leaf())
            return;

        for (auto i = node.begin; i < node.begin + node.n; ++i)
        {
            unsigned int index = bvh.entity(i);
            const Vector3r &x_w = vd.getPosition(index);
            const Vector3r x = R * (x_w - com2) + v1;
            Vector3r cp, n;
            Real dist;
            if (co2->collisionTest(x, m_tolerance, cp, n, dist))
            {
                const Vector3r cp_w = R.transpose() * cp + v2;
                const Vector3r n_w = R.transpose() * n;

#ifdef _DEBUG
                int tid = 0;
#else
                int tid = omp_get_thread_num();
#endif

                contacts_mt[tid].push_back({ 0, co1->m_bodyIndex, co2->m_bodyIndex, x_w, cp_w, n_w, dist, restitutionCoeff, frictionCoeff });
            }
        }
    };
    bvh.traverse_depth_first(predicate, cb);
}


void DistanceFieldCollisionDetection::collisionDetectionRBSolid(const ParticleData &pd, const unsigned int offset, const unsigned int numVert,
    DistanceFieldCollisionObject *co1, RigidBody *rb2, DistanceFieldCollisionObject *co2,
    const Real restitutionCoeff, const Real frictionCoeff
    , std::vector<std::vector<ContactData> > &contacts_mt
    )
{
    const Vector3r &com2 = rb2->getPosition();

    // remove the rotation of the main axis transformation that is performed
    // to get a diagonal inertia tensor since the distance function is
    // evaluated in local coordinates
    //
    // transformation world to local:
    // p_local = R_initial^T ( R_MAT R^T (p_world - x) - x_initial + x_MAT)
    //
    // transformation local to:
    // p_world = R R_MAT^T (R_initial p_local + x_initial - x_MAT) + x
    //
    const Matrix3r &R = rb2->getTransformationR();
    const Vector3r &v1 = rb2->getTransformationV1();
    const Vector3r &v2 = rb2->getTransformationV2();

    const PointCloudBSH &bvh = ((DistanceFieldCollisionDetection::DistanceFieldCollisionObject*) co1)->m_bvh;

    std::function<bool(unsigned int, unsigned int)> predicate = [&](unsigned int node_index, unsigned int depth)
    {
        const BoundingSphere &bs = bvh.hull(node_index);
        const Vector3r &sphere_x_w = bs.x();

        AlignedBox3r box3f;
        box3f.extend(co2->m_aabb.m_p[0]);
        box3f.extend(co2->m_aabb.m_p[1]);
        const Real dist = box3f.exteriorDistance(sphere_x_w);

        // Test if center of bounding sphere intersects AABB
        if (dist < bs.r())
        {
            // Test if distance of center of bounding sphere to collision object is smaller than the radius
            const Vector3r x = R * (sphere_x_w - com2) + v1;
            const double dist2 = co2->distance(x.template cast<double>(), m_tolerance);
            if (dist2 == std::numeric_limits<double>::max())
                return true;
            if (dist2 < bs.r())
                return true;
        }
        return false;
    };

    std::function<void(unsigned int, unsigned int)> cb = [&](unsigned int node_index, unsigned int depth)
    {
        auto const& node = bvh.node(node_index);
        if (!node.is_leaf())
            return;

        for (auto i = node.begin; i < node.begin + node.n; ++i)
        {
            unsigned int index = bvh.entity(i) + offset;
            const Vector3r &x_w = pd.getPosition(index);
            const Vector3r x = R * (x_w - com2) + v1;
            Vector3r cp, n;
            Real dist;
            if (co2->collisionTest(x, m_tolerance, cp, n, dist))
            {
                const Vector3r cp_w = R.transpose() * cp + v2;
                const Vector3r n_w = R.transpose() * n;

#ifdef _DEBUG
                int tid = 0;
#else
                int tid = omp_get_thread_num();
#endif
                contacts_mt[tid].push_back({ 1, index, co2->m_bodyIndex, x_w, cp_w, n_w, dist, restitutionCoeff, frictionCoeff });
            }
        }
    };

    bvh.traverse_depth_first(predicate, cb);
}

void DistanceFieldCollisionDetection::collisionDetectionSolidSolid(const ParticleData &pd, const unsigned int offset, const unsigned int numVert,
    DistanceFieldCollisionObject *co1, TetModel *tm2, DistanceFieldCollisionObject *co2,
    const Real restitutionCoeff, const Real frictionCoeff
    , std::vector<std::vector<ContactData> > &contacts_mt
)
{
    const PointCloudBSH &bvh1 = ((DistanceFieldCollisionDetection::DistanceFieldCollisionObject*) co1)->m_bvh;
    const TetMeshBSH &bvh2 = ((DistanceFieldCollisionDetection::DistanceFieldCollisionObject*) co2)->m_bvhTets;
    const unsigned int *indices = tm2->getParticleMesh().getTets().data();
    const unsigned int offset2 = tm2->getIndexOffset();


    // callback function for BVH which is called if a leaf node in the point cloud BVH
    // has a collision with a leaf node in the tet BVH
    std::function<void(unsigned int, unsigned int)> cb = [&](unsigned int node_index1, unsigned int node_index2)
    {
        auto const& node1 = bvh1.node(node_index1);
        auto const& node2 = bvh2.node(node_index2);

        // loop over all primitives (points, tets) in the leaf nodes
        for (auto i = node1.begin; i < node1.begin + node1.n; ++i)
        {
            for (auto j = node2.begin; j < node2.begin + node2.n; ++j)
            {
                // Get sample point
                unsigned int index = bvh1.entity(i) + offset;
                const Vector3r &x_w = pd.getPosition(index);

                // Get tet
                const unsigned int tetIndex = bvh2.entity(j);
                const Vector3r &x0 = pd.getPosition(indices[4 * tetIndex] + offset2);
                const Vector3r &x1 = pd.getPosition(indices[4 * tetIndex + 1] + offset2);
                const Vector3r &x2 = pd.getPosition(indices[4 * tetIndex + 2] + offset2);
                const Vector3r &x3 = pd.getPosition(indices[4 * tetIndex + 3] + offset2);

                // Compute barycentric coordinates of point in tet
                Matrix3r A;
                A.col(0) = x1 - x0;
                A.col(1) = x2 - x0;
                A.col(2) = x3 - x0;
                Vector3r bary = A.inverse() * (x_w - x0);

                // check if point lies in tet using barycentric coordinates
                if ((bary[0] >= 0.0) && (bary[1] >= 0.0) && (bary[2] >= 0.0) &&
                    (bary[0] + bary[1] + bary[2] <= 1.0))
                {
                    // use barycentric coordinates to determine position of the point in the reference space of the tet
                    const Vector3r &X0 = pd.getPosition0(indices[4 * tetIndex] + offset2);
                    const Vector3r &X1 = pd.getPosition0(indices[4 * tetIndex + 1] + offset2);
                    const Vector3r &X2 = pd.getPosition0(indices[4 * tetIndex + 2] + offset2);
                    const Vector3r &X3 = pd.getPosition0(indices[4 * tetIndex + 3] + offset2);

                    Matrix3r A0;
                    A0.col(0) = X1 - X0;
                    A0.col(1) = X2 - X0;
                    A0.col(2) = X3 - X0;

                    // point in reference space of the tet
                    const Vector3r X = X0 + A0 * bary;

                    Vector3r cp_l, n_l;
                    Real dist;

                    // apply inverse initial transform to transform the point in the space of the
                    // signed distance field
                    const Vector3r X_l = (tm2->getInitialR().transpose() * (X - tm2->getInitialX()));

                    // perform collision test with distance field to get closest point on surface
                    //if (co2->collisionTest(X_l, m_tolerance, cp_l, n_l, dist))
                    if (co2->collisionTest(X_l, 0.0, cp_l, n_l, dist))
                    {
                        unsigned int cp_tetIndex;
                        Vector3r cp_bary;

                        // transform closest point on surface back to the reference space of the tet model
                        const Vector3r cp0 = (tm2->getInitialR() * cp_l + tm2->getInitialX());

                        // find the tet which contains the resulting point
                        if (findRefTetAt(pd, tm2, co2, cp0, cp_tetIndex, cp_bary))
                        {
                            // if we are in another tet, update matrix A
                            Vector3r cp_w;
                            if (cp_tetIndex != tetIndex)
                            {
                                const Vector3r &x0 = pd.getPosition(indices[4 * cp_tetIndex] + offset2);
                                const Vector3r &x1 = pd.getPosition(indices[4 * cp_tetIndex + 1] + offset2);
                                const Vector3r &x2 = pd.getPosition(indices[4 * cp_tetIndex + 2] + offset2);
                                const Vector3r &x3 = pd.getPosition(indices[4 * cp_tetIndex + 3] + offset2);
                                A.col(0) = x1 - x0;
                                A.col(1) = x2 - x0;
                                A.col(2) = x3 - x0;

                                // compute world space contact point in body 2
                                cp_w = x0 + A * cp_bary;
                            }
                            else
                                // compute world space contact point in body 2
                                cp_w = x0 + A * cp_bary;

#ifdef _DEBUG
                            int tid = 0;
#else
                            int tid = omp_get_thread_num();
#endif

                            Vector3r n_w = cp_w - x_w;

                            // normalize normal vector
                            const Real dist = (x_w - cp_w).norm();
                            if (dist > 1.0e-6)
                                n_w /= dist;

                            contacts_mt[tid].push_back({ 2, index, co2->m_bodyIndex, x_w, cp_w, n_w, dist, restitutionCoeff, frictionCoeff, tetIndex, cp_tetIndex, bary, cp_bary });
                        }
                    }
                }
            }
        }
    };

    BVHTest::traverse(bvh1, bvh2, cb);

}

bool DistanceFieldCollisionDetection::isDistanceFieldCollisionObject(CollisionObject *co) const
{
    return (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionBox::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionSphere::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionTorus::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionCylinder::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionHollowBox::TYPE_ID) ||
        (co->getTypeId() == DistanceFieldCollisionDetection::DistanceFieldCollisionObjectWithoutGeometry::TYPE_ID);
}

void DistanceFieldCollisionDetection::addCollisionBox(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Vector3r &box, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionBox *cf = new DistanceFieldCollisionDetection::DistanceFieldCollisionBox();
    cf->m_bodyIndex = bodyIndex;
    cf->m_bodyType = bodyType;
    // distance function requires 0.5*box
    cf->m_box = 0.5*box;
    cf->m_bvh.init(vertices, numVertices);
    cf->m_bvh.construct();
    cf->m_testMesh = testMesh;
    if (invertSDF)
        cf->m_invertSDF = -1.0;
    m_collisionObjects.push_back(cf);
}

void DistanceFieldCollisionDetection::addCollisionSphere(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Real radius, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionSphere *cs = new DistanceFieldCollisionDetection::DistanceFieldCollisionSphere();
    cs->m_bodyIndex = bodyIndex;
    cs->m_bodyType = bodyType;
    cs->m_radius = radius;
    cs->m_bvh.init(vertices, numVertices);
    cs->m_bvh.construct();
    cs->m_testMesh = testMesh;
    if (invertSDF)
        cs->m_invertSDF = -1.0;
    m_collisionObjects.push_back(cs);
}

void DistanceFieldCollisionDetection::addCollisionTorus(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Vector2r &radii, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionTorus *ct = new DistanceFieldCollisionDetection::DistanceFieldCollisionTorus();
    ct->m_bodyIndex = bodyIndex;
    ct->m_bodyType = bodyType;
    ct->m_radii = radii;
    ct->m_bvh.init(vertices, numVertices);
    ct->m_bvh.construct();
    ct->m_testMesh = testMesh;
    if (invertSDF)
        ct->m_invertSDF = -1.0;
    m_collisionObjects.push_back(ct);
}

void DistanceFieldCollisionDetection::addCollisionCylinder(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Vector2r &dim, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionCylinder *ct = new DistanceFieldCollisionDetection::DistanceFieldCollisionCylinder();
    ct->m_bodyIndex = bodyIndex;
    ct->m_bodyType = bodyType;
    ct->m_dim = dim;
    // distance function uses height/2
    ct->m_dim[1] *= 0.5;
    ct->m_bvh.init(vertices, numVertices);
    ct->m_bvh.construct();
    ct->m_testMesh = testMesh;
    if (invertSDF)
        ct->m_invertSDF = -1.0;
    m_collisionObjects.push_back(ct);
}

void DistanceFieldCollisionDetection::addCollisionHollowSphere(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Real radius, const Real thickness, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere *cs = new DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere();
    cs->m_bodyIndex = bodyIndex;
    cs->m_bodyType = bodyType;
    cs->m_radius = radius;
    cs->m_thickness = thickness;
    cs->m_bvh.init(vertices, numVertices);
    cs->m_bvh.construct();
    cs->m_testMesh = testMesh;
    if (invertSDF)
        cs->m_invertSDF = -1.0;
    m_collisionObjects.push_back(cs);
}

void DistanceFieldCollisionDetection::addCollisionHollowBox(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const Vector3r &box, const Real thickness, const bool testMesh, const bool invertSDF)
{
    DistanceFieldCollisionDetection::DistanceFieldCollisionHollowBox *cf = new DistanceFieldCollisionDetection::DistanceFieldCollisionHollowBox();
    cf->m_bodyIndex = bodyIndex;
    cf->m_bodyType = bodyType;
    // distance function requires 0.5*box
    cf->m_box = 0.5*box;
    cf->m_thickness = thickness;
    cf->m_bvh.init(vertices, numVertices);
    cf->m_bvh.construct();
    cf->m_testMesh = testMesh;
    if (invertSDF)
        cf->m_invertSDF = -1.0;
    m_collisionObjects.push_back(cf);
}

void DistanceFieldCollisionDetection::addCollisionObjectWithoutGeometry(const unsigned int bodyIndex, const unsigned int bodyType, const Vector3r *vertices, const unsigned int numVertices, const bool testMesh)
{
    DistanceFieldCollisionObjectWithoutGeometry *co = new DistanceFieldCollisionObjectWithoutGeometry();
    co->m_bodyIndex = bodyIndex;
    co->m_bodyType = bodyType;
    co->m_bvh.init(vertices, numVertices);
    co->m_bvh.construct();
    co->m_testMesh = testMesh;
    co->m_invertSDF = 1.0;
    m_collisionObjects.push_back(co);
}

double DistanceFieldCollisionDetection::DistanceFieldCollisionBox::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const Eigen::Vector3d box_d = m_box.template cast<double>();
    const Eigen::Vector3d x_d = x.template cast<double>();
    const Eigen::Vector3d d(fabs(x_d.x()) - box_d.x(), fabs(x_d.y()) - box_d.y(), fabs(x_d.z()) - box_d.z());
    const Eigen::Vector3d max_d(std::max(d.x(), 0.0), std::max(d.y(), 0.0), std::max(d.z(), 0.0));
    return m_invertSDF*(std::min(std::max(d.x(), std::max(d.y(), d.z())), 0.0) + max_d.norm()) - static_cast<double>(tolerance);
}

double DistanceFieldCollisionDetection::DistanceFieldCollisionSphere::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const Eigen::Vector3d d = x.template cast<double>();
    const double dl = d.norm();
    return m_invertSDF*(dl - static_cast<double>(m_radius)) - static_cast<double>(tolerance);
}

bool DistanceFieldCollisionDetection::DistanceFieldCollisionSphere::collisionTest(const Vector3r &x, const Real tolerance, Vector3r &cp, Vector3r &n, Real &dist, const Real maxDist)
{
    const Vector3r d = x;
    const Real dl = d.norm();
    dist = m_invertSDF*(dl - m_radius) - tolerance;
    if (dist < maxDist)
    {
        if (dl < 1.e-6)
            n.setZero();
        else
            n = m_invertSDF * d / dl;

        cp = ((m_radius+tolerance) * n);
        return true;
    }
    return false;
}

double DistanceFieldCollisionDetection::DistanceFieldCollisionTorus::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const Eigen::Vector2d radii_d = m_radii.template cast<double>();
    const Eigen::Vector2d q(Vector2r(x.x(), x.z()).norm() - radii_d.x(), x.y());
    return m_invertSDF*(q.norm() - radii_d.y()) - tolerance;
}

double DistanceFieldCollisionDetection::DistanceFieldCollisionCylinder::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const double l = sqrt(x.x()*x.x() + x.z()*x.z());
    const Eigen::Vector2d d = Eigen::Vector2d(fabs(l), fabs(x.y())) - m_dim.template cast<double>();
    const Eigen::Vector2d max_d(std::max(d.x(), 0.0), std::max(d.y(), 0.0));
    return m_invertSDF*(std::min(std::max(d.x(), d.y()), 0.0) + max_d.norm()) - static_cast<double>(tolerance);
}


double DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const Eigen::Vector3d d = x.template cast<double>();
    const double dl = d.norm();
    return m_invertSDF*(fabs(dl - static_cast<double>(m_radius)) - static_cast<double>(m_thickness)) - static_cast<double>(tolerance);
}

bool DistanceFieldCollisionDetection::DistanceFieldCollisionHollowSphere::collisionTest(const Vector3r &x, const Real tolerance, Vector3r &cp, Vector3r &n, Real &dist, const Real maxDist)
{
    const Vector3r d = x;
    const Real dl = d.norm();
    dist = m_invertSDF*(fabs(dl - m_radius) - m_thickness) - tolerance;
    if (dist < maxDist)
    {
        if (dl < 1.e-6)
            n.setZero();
        else if (dl < m_radius)
            n = -m_invertSDF*d / dl;
        else
            n = m_invertSDF*d / dl;

        cp = x - dist * n;
        return true;
    }
    return false;
}

double DistanceFieldCollisionDetection::DistanceFieldCollisionHollowBox::distance(const Eigen::Vector3d &x, const Real tolerance)
{
    const Eigen::Vector3d box_d = m_box.template cast<double>();
    const Eigen::Vector3d x_d = x.template cast<double>();
    const Eigen::Vector3d d = x_d.cwiseAbs() - box_d;
    const Eigen::Vector3d max_d = d.cwiseMax(Eigen::Vector3d(0.0, 0.0, 0.0));
    return m_invertSDF * (fabs(std::min(d.maxCoeff(), 0.0) + max_d.norm()) - m_thickness) - static_cast<double>(tolerance);
}

void DistanceFieldCollisionDetection::DistanceFieldCollisionObject::approximateNormal(const Eigen::Vector3d &x, const Real tolerance, Vector3r &n)
{
    // approximate gradient
    double eps = 1.e-6;
    n.setZero();
    Eigen::Vector3d xTmp = x;
    for (unsigned int j = 0; j < 3; j++)
    {
        xTmp[j] += eps;

        double e_p, e_m;
        e_p = distance(xTmp, tolerance);
        xTmp[j] = x[j] - eps;
        e_m = distance(xTmp, tolerance);
        xTmp[j] = x[j];

        double res = (e_p - e_m) * (1.0 / (2.0*eps));

        n[j] = static_cast<Real>(res);
    }

    const Real norm2 = n.squaredNorm();
    if (norm2 < 1.e-6)
        n.setZero();
    else
        n = n / sqrt(norm2);
}


bool DistanceFieldCollisionDetection::DistanceFieldCollisionObject::collisionTest(const Vector3r &x, const Real tolerance, Vector3r &cp, Vector3r &n, Real &dist, const Real maxDist)
{
    const Real t_d = static_cast<Real>(tolerance);
    dist = static_cast<Real>(distance(x.template cast<double>(), t_d));
    if (dist < maxDist)
    {
        // approximate gradient
        const Eigen::Vector3d x_d = x.template cast<double>();

        approximateNormal(x_d, t_d, n);

        cp = (x - dist * n);
        return true;
    }
    return false;
}

void DistanceFieldCollisionDetection::DistanceFieldCollisionObject::initTetBVH(const Vector3r *vertices, const unsigned int numVertices, const unsigned int *indices, const unsigned int numTets, const Real tolerance)
{
    if (m_bodyType == CollisionDetection::CollisionObject::TetModelCollisionObjectType)
    {
        m_bvhTets.init(vertices, numVertices, indices, numTets, tolerance);
        m_bvhTets.construct();

        // ToDo: copy constructor
        m_bvhTets0.init(vertices, numVertices, indices, numTets, 0.0);
        m_bvhTets0.construct();

    }
}

bool DistanceFieldCollisionDetection::findRefTetAt(const ParticleData &pd, TetModel *tm, const DistanceFieldCollisionDetection::DistanceFieldCollisionObject *co, const Vector3r &X,
    unsigned int &tetIndex, Vector3r &barycentricCoordinates)
{
    const TetMeshBSH &bvh0 = ((DistanceFieldCollisionDetection::DistanceFieldCollisionObject*) co)->m_bvhTets0;
    const unsigned int *indices = tm->getParticleMesh().getTets().data();
    const unsigned int offset = tm->getIndexOffset();
    std::vector<Vector3r> bary;
    std::vector<unsigned int> tets;
    bary.reserve(100);
    tets.reserve(100);

    std::function<bool(unsigned int, unsigned int)> predicate = [&](unsigned int node_index, unsigned int depth)
    {
        const BoundingSphere &bs = bvh0.hull(node_index);
        return bs.contains(X);
    };
    std::function<void(unsigned int, unsigned int)> cb = [&](unsigned int node_index, unsigned int depth)
    {
        auto const& node = bvh0.node(node_index);
        if (!node.is_leaf())
            return;

        for (auto i = node.begin; i < node.begin + node.n; ++i)
        {
            const unsigned int tetIndex = bvh0.entity(i);

            // use barycentric coordinates to determine position in reference space
            const Vector3r &X0 = pd.getPosition0(indices[4 * tetIndex] + offset);
            const Vector3r &X1 = pd.getPosition0(indices[4 * tetIndex + 1] + offset);
            const Vector3r &X2 = pd.getPosition0(indices[4 * tetIndex + 2] + offset);
            const Vector3r &X3 = pd.getPosition0(indices[4 * tetIndex + 3] + offset);

            // Compute barycentric coordinates of point in tet
            Matrix3r A;
            A.col(0) = X1 - X0;
            A.col(1) = X2 - X0;
            A.col(2) = X3 - X0;
            bary.push_back(A.inverse() * (X - X0));
            tets.push_back(tetIndex);
        }
    };

    bvh0.traverse_depth_first(predicate, cb);

    if (bary.size() == 0)
        return false;

    // find best set of barycentric coordinates
    unsigned int index = 0;
    Real minError = REAL_MAX;
    for (unsigned int i = 0; i < bary.size(); i++)
    {
        // Determine if barycentric coordinates are negative and add distance to 0 as error
        Real error = std::max(static_cast<Real>(0.0), -bary[i][0]);
        error += std::max(static_cast<Real>(0.0), -bary[i][1]);
        error += std::max(static_cast<Real>(0.0), -bary[i][2]);

        // Determine if sum of barycentric coordinates is larger than one and add distance to 1 as error
        error += std::max(static_cast<Real>(0.0), bary[i][0] + bary[i][1] + bary[i][2] - static_cast<Real>(1.0));

        if (error < minError)
        {
            minError = error;
            index = i;
        }
    }
    barycentricCoordinates = bary[index];
    tetIndex = tets[index];
    return true;
}