Signed distance function

In EBGeometry we have encapsulated the concept of a signed distance function in an abstract class

/* EBGeometry
 * Copyright © 2022 Robert Marskar
 * Please refer to Copyright.txt and LICENSE in the EBGeometry root directory.
 */

/*!
  @file   EBGeometry_SignedDistanceFunction.hpp
  @brief  Abstract base class for representing a signed distance function.
  @author Robert Marskar
*/

#ifndef EBGeometry_SignedDistanceFunction
#define EBGeometry_SignedDistanceFunction

#include <memory>
#include <deque>

// Our includes
#include "EBGeometry_ImplicitFunction.hpp"
#include "EBGeometry_NamespaceHeader.hpp"

/*!
  @brief Abstract representation of a signed distance function.
  @details Users can put whatever they like in here, e.g. analytic functions,
  DCEL meshes, or DCEL meshes stored in full or compact BVH trees. The
  signedDistance function must be implemented by the user. When computing it,
  the user can apply transformation operators (rotations, scaling, translations)
  by calling transformPoint on the input coordinate.
*/
template <class T>
class SignedDistanceFunction : public ImplicitFunction<T>
{
public:
  /*!
    @brief Disallowed, use the full constructor
  */
  SignedDistanceFunction() = default;

  /*!
    @brief Destructor (does nothing)
  */
  virtual ~SignedDistanceFunction() = default;

  /*!
    @brief Implementation of ImplicitFunction::value
    @param[in] a_point 3D point.
  */
  virtual T
  value(const Vec3T<T>& a_point) const noexcept override final;

  /*!
    @brief Signed distance function.
    @param[in] a_point 3D point.
  */
  virtual T
  signedDistance(const Vec3T<T>& a_point) const noexcept = 0;

  /*!
    @brief Signed distance normal vector.
    @details Computed using finite differences with step a_delta
    @param[in] a_point 3D point
    @param[in] a_delta Finite difference step
  */
  inline virtual Vec3T<T>
  normal(const Vec3T<T>& a_point, const T& a_delta) const noexcept;
};

#include "EBGeometry_NamespaceFooter.hpp"

#include "EBGeometry_SignedDistanceFunctionImplem.hpp"

#endif

We point out that the BVH and DCEL classes are fundamentally also signed distance functions, and they also inherit from SignedDistanceFunction. The SignedDistanceFunction class also exists so that we have a common entry point for performing distance field manipulations like rotations and translations.

When implementing the signedDistance function, one can transform the input point by first calling transformPoint. The functions translate and rotate will translate or rotate the object. It is also possible to scale an object, but this is not simply a coordinate transform so it is implemented as a separate signed distance function. For example, in order to rotate a DCEL mesh (without using the BVH accelerator) we can implement the following signed distance function:

template <class T>
class MySignedDistanceFunction : public SignedDistanceFunction<T> {
public:
   T signedDistance(const Vec3T<T>& a_point) const noexcept override {
      return m_mesh->signedDistance(this->transformPoint(a_point));
   }

protected:
   // DCEL mesh object, must be constructed externally and
   // supplied to MyDistanceFunction (e.g. through the constructor).
   std::shared_ptr<EBGeometry::Dcel::MeshT<T> > m_mesh;
};

Alternatively, using a BVH structure:

template <class T, class P, class BV, int K>
class MySignedDistanceFunction : public SignedDistanceFunction<T> {
public:
   T signedDistance(const Vec3T<T>& a_point) const noexcept override {
      return m_bvh->signedDistance(this->transformPoint(a_point));
   }

protected:
   // BVH object, must be constructed externally
   // and supplied to MyDistanceFunction (e.g. through the constructor).
   std::shared_ptr<EBGeometry::BVH::LinearBVH<T, P, BV, K> > m_bvh;
};

Normal vector

The normal vector of EBGeometry::SignedDistanceFunction<T> is computed using centered finite differences:

\[n_i\left(\mathbf{x}\right) = \frac{1}{2\Delta}\left[S\left(\mathbf{x} + \Delta\mathbf{\hat{i}}\right) - S\left(\mathbf{x} - \Delta\mathbf{\hat{i}}\right)\right],\]

where \(i\) is a coordinate direction and \(\Delta > 0\). This is done for each component, and the normalized vector is then returned.

Transformations

The following transformations are possible:

  • Translation, which defines the operation \(\mathbf{x}^\prime = \mathbf{x} - \mathbf{t}\) where \(\mathbf{t}\) is a translation vector.

  • Rotation, which defines the operation \(\mathbf{x}^\prime = R\left(\mathbf{x}, \theta, a\right)\) where \(\mathbf{x}\) is rotated an angle \(\theta\) around the coordinate axis \(a\).

Transformations are applied sequentially. The APIs are as follows:

void translate(const Vec3T<T>& a_translation) noexcept;  // a_translation are Cartesian translations vector
void rotate(const T a_angle, const int a_axis) noexcept; // a_angle in degrees, and a_axis being the Cartesian axis

E.g. the following code will first translate, then 90 degrees about the \(x\)-axis.

MySignedDistanceFunction<float> sdf;

sdf.translate({1,0,0});
sdf.rotate(90, 0);

Note that if the transformations are to be applied, the implementation of signedDistance(...) must transform the input point, as shown in the examples above.

Rounding

Distance functions can be rounded by displacing the SDF by a specified distance. For example, given a distance functions \(S\left(\mathbf{x}\right)\), the rounded distance functions is

\[S_r\left(\mathbf{x}\right) = S\left(\mathbf{x}\right) - r,\]

where \(r\) is some rounding radius. Note that the rounding does not preserve the volume of the original SDF, so subsequent scaling of the object is usually necessary.

The rounded SDF is implemented in Source/EBGeometry_AnalyticDistanceFunctions.hpp

template <class T>
class RoundedSDF : public SignedDistanceFunction<T>
{
public:
  /*!
    @brief Disallowed weak construction
  */
  RoundedSDF() = delete;

  /*!
    @brief Rounded SDF. Rounds the input SDF
    @param[in] a_sdf  Input signed distance function.
    @param[in] a_curv Rounding radius.
  */
  RoundedSDF(const std::shared_ptr<SignedDistanceFunction<T>> a_sdf, const T a_curv)
  {
    m_sdf  = a_sdf;
    m_curv = a_curv;
  }

  /*!
    @brief Destructor
  */
  virtual ~RoundedSDF()
  {}

  /*!
    @brief Signed distance field.
  */
  virtual T
  signedDistance(const Vec3T<T>& a_point) const noexcept override
  {
    return m_sdf->signedDistance(a_point) - m_curv;
  }

protected:
  /*!
    @brief Original signed distance function
  */
  std::shared_ptr<const SignedDistanceFunction<T>> m_sdf;

  /*!
    @brief Rounding radius
  */
  T m_curv;

To use it, simply pass an SDF into the constructor and use the new distance function.

Scaling

Scaling of distance functions are possible through the transformation

\[S_c\left(\mathbf{x}\right) = c S\left(\frac{\mathbf{x}}{c}\right),\]

where \(c\) is a scaling factor. We point out that anisotropic stretching does not preserve the distance field.

The rounded SDF is implemented in Source/EBGeometry_AnalyticDistanceFunctions.hpp

  @brief Scaled signed distance function.
*/
template <class T>
class ScaledSDF : public SignedDistanceFunction<T>
{
public:
  /*!
    @brief Disallowed weak construction
  */
  ScaledSDF() = delete;

  /*!
    @brief Scaled SDF.
    @param[in] a_sdf   Input signed distance function.
    @param[in] a_scale Scaling factor.
  */
  ScaledSDF(const std::shared_ptr<SignedDistanceFunction<T>> a_sdf, const T a_scale)
  {
    m_sdf   = a_sdf;
    m_scale = a_scale;
  }

  /*!
    @brief Destructor
  */
  virtual ~ScaledSDF()
  {}

  /*!
    @brief Signed distance field.
    @param[in] a_point Input point.
  */
  virtual T
  signedDistance(const Vec3T<T>& a_point) const noexcept override
  {
    return (m_sdf->signedDistance(a_point / m_scale)) * m_scale;
  }

protected:
  /*!
    @brief Original signed distance function
  */
  std::shared_ptr<const SignedDistanceFunction<T>> m_sdf;

  /*!
    @brief Scaling factor.

Analytic functions

Above, we have shown how users can supply a DCEL or BVH structure to implement SignedDistanceFunction. In addition, the file Source/EBGeometry_AnalyticSignedDistanceFunctions.hpp defines various other analytic shapes such as:

  • Sphere

    template <class T>
    class SphereSDF : public SignedDistanceFunction<T>
    
  • Box

    template <class T>
    class BoxSDF : public SignedDistanceFunction<T>
    
  • Torus

    template <class T>
    class TorusSDF : public SignedDistanceFunction<T>
    
  • Capped cylinder

    template <class T>
    class CylinderSDF : public SignedDistanceFunction<T>
    
  • Infinite cylinder

    template <class T>
    class InfiniteCylinderSDF : public SignedDistanceFunction<T>
    
  • Capsule/rounded cylinder

    template <class T>
    class CapsuleSDF : public SignedDistanceFunction<T>
    
  • Infinite cone

    template <class T>
    class InfiniteConeSDF : public SignedDistanceFunction<T>
    
  • Cone

    template <class T>
    class ConeSDF : public SignedDistanceFunction<T>