Skip to content

geometry_3d.hpp

SECTIONGeometry INCLUDEnoya/geometry/geometry_3d.hpp

提供三维点向量、混合积、点线面距离、平面投影、线面交及四面体有向体积;退化直线和平面返回空结果。

Complexity: Time: O(1) per primitive. Space: O(1).

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

/// @complexity Time: O(1) per primitive. Space: O(1).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <optional>

namespace noya {

/// @brief Three-dimensional point/vector with lexicographic order.
/// Vector arithmetic and products use T; choose T wide enough for intermediate
/// products. Metric operations convert coordinates before subtracting them.
template <class T> struct point3 {
  T x{};
  T y{};
  T z{};

  point3() = default;
  point3(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {}

  point3 &operator+=(const point3 &rhs) {
    x += rhs.x;
    y += rhs.y;
    z += rhs.z;
    return *this;
  }
  point3 &operator-=(const point3 &rhs) {
    x -= rhs.x;
    y -= rhs.y;
    z -= rhs.z;
    return *this;
  }
  point3 &operator*=(const T &scl) {
    x *= scl;
    y *= scl;
    z *= scl;
    return *this;
  }
  point3 &operator/=(const T &scl) {
    x /= scl;
    y /= scl;
    z /= scl;
    return *this;
  }

  friend point3 operator+(point3 lhs, const point3 &rhs) { return lhs += rhs; }
  friend point3 operator-(point3 lhs, const point3 &rhs) { return lhs -= rhs; }
  friend point3 operator*(point3 val, const T &scl) { return val *= scl; }
  friend point3 operator*(const T &scl, point3 val) { return val *= scl; }
  friend point3 operator/(point3 val, const T &scl) { return val /= scl; }
  friend bool operator==(const point3 &, const point3 &) = default;
  friend bool operator<(const point3 &lhs, const point3 &rhs) {
    if (lhs.x != rhs.x)
      return lhs.x < rhs.x;
    if (lhs.y != rhs.y)
      return lhs.y < rhs.y;
    return lhs.z < rhs.z;
  }
};

/// @brief Plane through p with normal n; n must be nonzero for a valid plane.
template <class T> struct plane3 {
  point3<T> p;
  point3<T> n;
};

template <class T> T dot(const point3<T> &a, const point3<T> &b) {
  return a.x * b.x + a.y * b.y + a.z * b.z;
}

template <class T> point3<T> cross(const point3<T> &a, const point3<T> &b) {
  return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}

template <class T> T norm2(const point3<T> &val) { return dot(val, val); }

/// @brief Signed volume of the parallelepiped spanned by a, b, and c.
template <class T>
T scalar_triple_product(const point3<T> &a, const point3<T> &b,
                        const point3<T> &c) {
  return dot(a, cross(b, c));
}

namespace geometry_3d_internal {

template <class T> point3<long double> cast_point(const point3<T> &val) {
  return {static_cast<long double>(val.x), static_cast<long double>(val.y),
          static_cast<long double>(val.z)};
}

inline long double length(const point3<long double> &val) {
  return std::hypot(val.x, val.y, val.z);
}

} // namespace geometry_3d_internal

template <class T>
long double distance(const point3<T> &a, const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  return geometry_3d_internal::length(cast_point(a) - cast_point(b));
}

/// @brief Distance to the infinite line through a and b; nullopt if a == b.
template <class T>
std::optional<long double> point_line_distance(const point3<T> &p,
                                               const point3<T> &a,
                                               const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return std::nullopt;
  return length(cross(cast_point(p) - cast_point(a), dir / len));
}

/// @brief Distance to the closed segment [a, b]; equal endpoints form a point.
template <class T>
long double point_segment_distance(const point3<T> &p, const point3<T> &a,
                                   const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto rel = cast_point(p) - cast_point(a);
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return length(rel);
  dir /= len;
  long double pro = std::clamp(dot(rel, dir), 0.0L, len);
  return length(rel - dir * pro);
}

/// @brief Plane through three points with a unit normal following (b-a)x(c-a).
/// Return nullopt for repeated points or when the sine of their angle is at
/// most eps. eps is dimensionless and must be nonnegative.
template <class T>
std::optional<plane3<long double>>
plane_through_points(const point3<T> &a, const point3<T> &b, const point3<T> &c,
                     long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto lhs = cast_point(b) - cast_point(a);
  auto rhs = cast_point(c) - cast_point(a);
  long double al = length(lhs), bl = length(rhs);
  if (al == 0 || bl == 0)
    return std::nullopt;
  auto nm = cross(lhs / al, rhs / bl);
  long double nl = length(nm);
  if (nl <= eps)
    return std::nullopt;
  return plane3<long double>{cast_point(a), nm / nl};
}

/// @brief Perpendicular distance to a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<long double> point_plane_distance(const point3<T> &p,
                                                const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  return std::abs(dot(cast_point(p) - cast_point(pl.p), nm / len));
}

/// @brief Orthogonal projection onto a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<point3<long double>> plane_projection(const point3<T> &p,
                                                    const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  nm /= len;
  auto pt = cast_point(p);
  return pt - nm * dot(pt - cast_point(pl.p), nm);
}

/// @brief Unique intersection of the infinite line (a, b) with a plane.
/// Return nullopt for a degenerate line/plane, a parallel line, or a line
/// contained in the plane. eps bounds the absolute dot product of the unit
/// line direction and unit plane normal, so scaling the normal has no effect.
template <class T, class U>
std::optional<point3<long double>>
line_plane_intersection(const point3<T> &a, const point3<T> &b,
                        const plane3<U> &pl, long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  auto nm = cast_point(pl.n);
  long double dl = length(dir), nl = length(nm);
  if (dl == 0 || nl == 0)
    return std::nullopt;
  dir /= dl;
  nm /= nl;
  long double den = dot(dir, nm);
  if (std::abs(den) <= eps)
    return std::nullopt;
  auto pt = cast_point(a);
  return pt + dir * (dot(cast_point(pl.p) - pt, nm) / den);
}

/// @brief Signed tetrahedron volume det(b-a, c-a, d-a)/6, in long double.
template <class T>
long double signed_tetrahedron_volume(const point3<T> &a, const point3<T> &b,
                                      const point3<T> &c, const point3<T> &d) {
  using geometry_3d_internal::cast_point;
  auto o = cast_point(a);
  return scalar_triple_product(cast_point(b) - o, cast_point(c) - o,
                               cast_point(d) - o) /
         6;
}

} // namespace noya

 // NOYA_GEOMETRY_3D_HPP
#ifndef NOYA_GEOMETRY_3D_HPP
#define NOYA_GEOMETRY_3D_HPP 1

/// @complexity Time: O(1) per primitive. Space: O(1).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <optional>

namespace noya {

/// @brief Three-dimensional point/vector with lexicographic order.
/// Vector arithmetic and products use T; choose T wide enough for intermediate
/// products. Metric operations convert coordinates before subtracting them.
template <class T> struct point3 {
  T x{};
  T y{};
  T z{};

  point3() = default;
  point3(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {}

  point3 &operator+=(const point3 &rhs) {
    x += rhs.x;
    y += rhs.y;
    z += rhs.z;
    return *this;
  }
  point3 &operator-=(const point3 &rhs) {
    x -= rhs.x;
    y -= rhs.y;
    z -= rhs.z;
    return *this;
  }
  point3 &operator*=(const T &scl) {
    x *= scl;
    y *= scl;
    z *= scl;
    return *this;
  }
  point3 &operator/=(const T &scl) {
    x /= scl;
    y /= scl;
    z /= scl;
    return *this;
  }

  friend point3 operator+(point3 lhs, const point3 &rhs) { return lhs += rhs; }
  friend point3 operator-(point3 lhs, const point3 &rhs) { return lhs -= rhs; }
  friend point3 operator*(point3 val, const T &scl) { return val *= scl; }
  friend point3 operator*(const T &scl, point3 val) { return val *= scl; }
  friend point3 operator/(point3 val, const T &scl) { return val /= scl; }
  friend bool operator==(const point3 &, const point3 &) = default;
  friend bool operator<(const point3 &lhs, const point3 &rhs) {
    if (lhs.x != rhs.x)
      return lhs.x < rhs.x;
    if (lhs.y != rhs.y)
      return lhs.y < rhs.y;
    return lhs.z < rhs.z;
  }
};

/// @brief Plane through p with normal n; n must be nonzero for a valid plane.
template <class T> struct plane3 {
  point3<T> p;
  point3<T> n;
};

template <class T> T dot(const point3<T> &a, const point3<T> &b) {
  return a.x * b.x + a.y * b.y + a.z * b.z;
}

template <class T> point3<T> cross(const point3<T> &a, const point3<T> &b) {
  return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}

template <class T> T norm2(const point3<T> &val) { return dot(val, val); }

/// @brief Signed volume of the parallelepiped spanned by a, b, and c.
template <class T>
T scalar_triple_product(const point3<T> &a, const point3<T> &b,
                        const point3<T> &c) {
  return dot(a, cross(b, c));
}

namespace geometry_3d_internal {

template <class T> point3<long double> cast_point(const point3<T> &val) {
  return {static_cast<long double>(val.x), static_cast<long double>(val.y),
          static_cast<long double>(val.z)};
}

inline long double length(const point3<long double> &val) {
  return std::hypot(val.x, val.y, val.z);
}

} // namespace geometry_3d_internal

template <class T>
long double distance(const point3<T> &a, const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  return geometry_3d_internal::length(cast_point(a) - cast_point(b));
}

/// @brief Distance to the infinite line through a and b; nullopt if a == b.
template <class T>
std::optional<long double> point_line_distance(const point3<T> &p,
                                               const point3<T> &a,
                                               const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return std::nullopt;
  return length(cross(cast_point(p) - cast_point(a), dir / len));
}

/// @brief Distance to the closed segment [a, b]; equal endpoints form a point.
template <class T>
long double point_segment_distance(const point3<T> &p, const point3<T> &a,
                                   const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto rel = cast_point(p) - cast_point(a);
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return length(rel);
  dir /= len;
  long double pro = std::clamp(dot(rel, dir), 0.0L, len);
  return length(rel - dir * pro);
}

/// @brief Plane through three points with a unit normal following (b-a)x(c-a).
/// Return nullopt for repeated points or when the sine of their angle is at
/// most eps. eps is dimensionless and must be nonnegative.
template <class T>
std::optional<plane3<long double>>
plane_through_points(const point3<T> &a, const point3<T> &b, const point3<T> &c,
                     long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto lhs = cast_point(b) - cast_point(a);
  auto rhs = cast_point(c) - cast_point(a);
  long double al = length(lhs), bl = length(rhs);
  if (al == 0 || bl == 0)
    return std::nullopt;
  auto nm = cross(lhs / al, rhs / bl);
  long double nl = length(nm);
  if (nl <= eps)
    return std::nullopt;
  return plane3<long double>{cast_point(a), nm / nl};
}

/// @brief Perpendicular distance to a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<long double> point_plane_distance(const point3<T> &p,
                                                const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  return std::abs(dot(cast_point(p) - cast_point(pl.p), nm / len));
}

/// @brief Orthogonal projection onto a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<point3<long double>> plane_projection(const point3<T> &p,
                                                    const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  nm /= len;
  auto pt = cast_point(p);
  return pt - nm * dot(pt - cast_point(pl.p), nm);
}

/// @brief Unique intersection of the infinite line (a, b) with a plane.
/// Return nullopt for a degenerate line/plane, a parallel line, or a line
/// contained in the plane. eps bounds the absolute dot product of the unit
/// line direction and unit plane normal, so scaling the normal has no effect.
template <class T, class U>
std::optional<point3<long double>>
line_plane_intersection(const point3<T> &a, const point3<T> &b,
                        const plane3<U> &pl, long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  auto nm = cast_point(pl.n);
  long double dl = length(dir), nl = length(nm);
  if (dl == 0 || nl == 0)
    return std::nullopt;
  dir /= dl;
  nm /= nl;
  long double den = dot(dir, nm);
  if (std::abs(den) <= eps)
    return std::nullopt;
  auto pt = cast_point(a);
  return pt + dir * (dot(cast_point(pl.p) - pt, nm) / den);
}

/// @brief Signed tetrahedron volume det(b-a, c-a, d-a)/6, in long double.
template <class T>
long double signed_tetrahedron_volume(const point3<T> &a, const point3<T> &b,
                                      const point3<T> &c, const point3<T> &d) {
  using geometry_3d_internal::cast_point;
  auto o = cast_point(a);
  return scalar_triple_product(cast_point(b) - o, cast_point(c) - o,
                               cast_point(d) - o) /
         6;
}

} // namespace noya

#endif // NOYA_GEOMETRY_3D_HPP
/// @complexity Time: O(1) per primitive. Space: O(1).

#include <algorithm>
#include <cassert>
#include <cmath>
#include <optional>

namespace noya {

/// @brief Three-dimensional point/vector with lexicographic order.
/// Vector arithmetic and products use T; choose T wide enough for intermediate
/// products. Metric operations convert coordinates before subtracting them.
template <class T> struct point3 {
  T x{};
  T y{};
  T z{};

  point3() = default;
  point3(T x_, T y_, T z_) : x(x_), y(y_), z(z_) {}

  point3 &operator+=(const point3 &rhs) {
    x += rhs.x;
    y += rhs.y;
    z += rhs.z;
    return *this;
  }
  point3 &operator-=(const point3 &rhs) {
    x -= rhs.x;
    y -= rhs.y;
    z -= rhs.z;
    return *this;
  }
  point3 &operator*=(const T &scl) {
    x *= scl;
    y *= scl;
    z *= scl;
    return *this;
  }
  point3 &operator/=(const T &scl) {
    x /= scl;
    y /= scl;
    z /= scl;
    return *this;
  }

  friend point3 operator+(point3 lhs, const point3 &rhs) { return lhs += rhs; }
  friend point3 operator-(point3 lhs, const point3 &rhs) { return lhs -= rhs; }
  friend point3 operator*(point3 val, const T &scl) { return val *= scl; }
  friend point3 operator*(const T &scl, point3 val) { return val *= scl; }
  friend point3 operator/(point3 val, const T &scl) { return val /= scl; }
  friend bool operator==(const point3 &, const point3 &) = default;
  friend bool operator<(const point3 &lhs, const point3 &rhs) {
    if (lhs.x != rhs.x)
      return lhs.x < rhs.x;
    if (lhs.y != rhs.y)
      return lhs.y < rhs.y;
    return lhs.z < rhs.z;
  }
};

/// @brief Plane through p with normal n; n must be nonzero for a valid plane.
template <class T> struct plane3 {
  point3<T> p;
  point3<T> n;
};

template <class T> T dot(const point3<T> &a, const point3<T> &b) {
  return a.x * b.x + a.y * b.y + a.z * b.z;
}

template <class T> point3<T> cross(const point3<T> &a, const point3<T> &b) {
  return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
}

template <class T> T norm2(const point3<T> &val) { return dot(val, val); }

/// @brief Signed volume of the parallelepiped spanned by a, b, and c.
template <class T>
T scalar_triple_product(const point3<T> &a, const point3<T> &b,
                        const point3<T> &c) {
  return dot(a, cross(b, c));
}

namespace geometry_3d_internal {

template <class T> point3<long double> cast_point(const point3<T> &val) {
  return {static_cast<long double>(val.x), static_cast<long double>(val.y),
          static_cast<long double>(val.z)};
}

inline long double length(const point3<long double> &val) {
  return std::hypot(val.x, val.y, val.z);
}

} // namespace geometry_3d_internal

template <class T>
long double distance(const point3<T> &a, const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  return geometry_3d_internal::length(cast_point(a) - cast_point(b));
}

/// @brief Distance to the infinite line through a and b; nullopt if a == b.
template <class T>
std::optional<long double> point_line_distance(const point3<T> &p,
                                               const point3<T> &a,
                                               const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return std::nullopt;
  return length(cross(cast_point(p) - cast_point(a), dir / len));
}

/// @brief Distance to the closed segment [a, b]; equal endpoints form a point.
template <class T>
long double point_segment_distance(const point3<T> &p, const point3<T> &a,
                                   const point3<T> &b) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto rel = cast_point(p) - cast_point(a);
  auto dir = cast_point(b) - cast_point(a);
  long double len = length(dir);
  if (len == 0)
    return length(rel);
  dir /= len;
  long double pro = std::clamp(dot(rel, dir), 0.0L, len);
  return length(rel - dir * pro);
}

/// @brief Plane through three points with a unit normal following (b-a)x(c-a).
/// Return nullopt for repeated points or when the sine of their angle is at
/// most eps. eps is dimensionless and must be nonnegative.
template <class T>
std::optional<plane3<long double>>
plane_through_points(const point3<T> &a, const point3<T> &b, const point3<T> &c,
                     long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto lhs = cast_point(b) - cast_point(a);
  auto rhs = cast_point(c) - cast_point(a);
  long double al = length(lhs), bl = length(rhs);
  if (al == 0 || bl == 0)
    return std::nullopt;
  auto nm = cross(lhs / al, rhs / bl);
  long double nl = length(nm);
  if (nl <= eps)
    return std::nullopt;
  return plane3<long double>{cast_point(a), nm / nl};
}

/// @brief Perpendicular distance to a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<long double> point_plane_distance(const point3<T> &p,
                                                const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  return std::abs(dot(cast_point(p) - cast_point(pl.p), nm / len));
}

/// @brief Orthogonal projection onto a plane; nullopt for a zero normal.
template <class T, class U>
std::optional<point3<long double>> plane_projection(const point3<T> &p,
                                                    const plane3<U> &pl) {
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto nm = cast_point(pl.n);
  long double len = length(nm);
  if (len == 0)
    return std::nullopt;
  nm /= len;
  auto pt = cast_point(p);
  return pt - nm * dot(pt - cast_point(pl.p), nm);
}

/// @brief Unique intersection of the infinite line (a, b) with a plane.
/// Return nullopt for a degenerate line/plane, a parallel line, or a line
/// contained in the plane. eps bounds the absolute dot product of the unit
/// line direction and unit plane normal, so scaling the normal has no effect.
template <class T, class U>
std::optional<point3<long double>>
line_plane_intersection(const point3<T> &a, const point3<T> &b,
                        const plane3<U> &pl, long double eps = 1e-12L) {
  assert(eps >= 0);
  using geometry_3d_internal::cast_point;
  using geometry_3d_internal::length;
  auto dir = cast_point(b) - cast_point(a);
  auto nm = cast_point(pl.n);
  long double dl = length(dir), nl = length(nm);
  if (dl == 0 || nl == 0)
    return std::nullopt;
  dir /= dl;
  nm /= nl;
  long double den = dot(dir, nm);
  if (std::abs(den) <= eps)
    return std::nullopt;
  auto pt = cast_point(a);
  return pt + dir * (dot(cast_point(pl.p) - pt, nm) / den);
}

/// @brief Signed tetrahedron volume det(b-a, c-a, d-a)/6, in long double.
template <class T>
long double signed_tetrahedron_volume(const point3<T> &a, const point3<T> &b,
                                      const point3<T> &c, const point3<T> &d) {
  using geometry_3d_internal::cast_point;
  auto o = cast_point(a);
  return scalar_triple_product(cast_point(b) - o, cast_point(c) - o,
                               cast_point(d) - o) /
         6;
}

} // namespace noya

 // NOYA_GEOMETRY_3D_HPP