Point-Edge Distance

Next, we calculate the point-edge distance and its derivatives. These will be used to solve for the contact forces. For a node and an edge , their squared distance is defined as

which is the closest squared distance between and any point on .

Remark 21.2.1 (Distance Calculation Optimization). Here, we use the squared unsigned distances for evaluating the contact energies. This approach avoids taking square roots, which can complicate the expression of the derivatives and increase numerical rounding errors during computation. Additionally, unsigned distances can be directly extended for codimensional pairs, such as point-point pairs, which are useful when simulating particle contacts in 2D. They also do not suffer from locking issues, as signed distances do, when there are large displacements.

Fortunately, is a piece-wise smooth function w.r.t. the DOFs: where the smooth expression can be determined by checking whether the node is inside the orthogonal span of the edge. Given these smooth expressions, we can differentiate each of them and obtain the derivatives of the point-edge distance function. The implementations are as follows:

Implementation 21.2.1 (Point-Edge distance calculation (Hessian omitted), PointEdgeDistance.py).

import numpy as np

import distance.PointPointDistance as PP
import distance.PointLineDistance as PL

def val(p, e0, e1):
    e = e1 - e0
    ratio = np.dot(e, p - e0) / np.dot(e, e)
    if ratio < 0:    # point(p)-point(e0) expression
        return PP.val(p, e0)
    elif ratio > 1:  # point(p)-point(e1) expression
        return PP.val(p, e1)
    else:            # point(p)-line(e0e1) expression
        return PL.val(p, e0, e1)

def grad(p, e0, e1):
    e = e1 - e0
    ratio = np.dot(e, p - e0) / np.dot(e, e)
    if ratio < 0:    # point(p)-point(e0) expression
        g_PP = PP.grad(p, e0)
        return np.reshape([g_PP[0:2], g_PP[2:4], np.array([0.0, 0.0])], (1, 6))[0]
    elif ratio > 1:  # point(p)-point(e1) expression
        g_PP = PP.grad(p, e1)
        return np.reshape([g_PP[0:2], np.array([0.0, 0.0]), g_PP[2:4]], (1, 6))[0]
    else:            # point(p)-line(e0e1) expression
        return PL.grad(p, e0, e1)

It can be verified that the point-edge distance function is -continuous everywhere, including at the interfaces between different segments. For the point-point case, we have:

Implementation 21.2.2 (Point-Point distance calculation, PointPointDistance.py).

import numpy as np

def val(p0, p1):
    e = p0 - p1
    return np.dot(e, e)

def grad(p0, p1):
    e = p0 - p1
    return np.reshape([2 * e, -2 * e], (1, 4))[0]

def hess(p0, p1):
    H = np.array([[0.0] * 4] * 4)
    H[0, 0] = H[1, 1] = H[2, 2] = H[3, 3] = 2
    H[0, 2] = H[1, 3] = H[2, 0] = H[3, 1] = -2
    return H

For the point-line case, the distance evaluations can be implemented as follows, and the derivatives can be obtained using symbolic differentiation tools.

Implementation 21.2.3 (Point-Line distance calculation (Hessian omitted), PointLineDistance.py).

import numpy as np

def val(p, e0, e1):
    e = e1 - e0
    numerator = e[1] * p[0] - e[0] * p[1] + e1[0] * e0[1] - e1[1] * e0[0]
    return numerator * numerator / np.dot(e, e)

def grad(p, e0, e1):
    g = np.array([0.0] * 6)
    t13 = -e1[0] + e0[0]
    t14 = -e1[1] + e0[1]
    t23 = 1.0 / (t13 * t13 + t14 * t14)
    t25 = ((e0[0] * e1[1] + -(e0[1] * e1[0])) + t14 * p[0]) + -(t13 * p[1])
    t24 = t23 * t23
    t26 = t25 * t25
    t27 = (e0[0] * 2.0 + -(e1[0] * 2.0)) * t24 * t26
    t26 *= (e0[1] * 2.0 + -(e1[1] * 2.0)) * t24
    g[0] = t14 * t23 * t25 * 2.0
    g[1] = t13 * t23 * t25 * -2.0
    t24 = t23 * t25
    g[2] = -t27 - t24 * (-e1[1] + p[1]) * 2.0
    g[3] = -t26 + t24 * (-e1[0] + p[0]) * 2.0
    g[4] = t27 + t24 * (p[1] - e0[1]) * 2.0
    g[5] = t26 - t24 * (p[0] - e0[0]) * 2.0
    return g