/* Copyright (C) 2017 Eric Wasylishen This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA See file, 'COPYING', for details. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "common/mathlib.hh" template class qvec { protected: std::array v; template friend class qvec; public: using value_type = T; constexpr qvec() = default; #ifdef __clang__ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunused-value" #endif template, T>>> constexpr qvec(Args... a) : v({}) { constexpr size_t count = sizeof...(Args); // special case for single argument if constexpr (count == 1) { for (auto &e : v) ((e = a, true) || ...); } // multiple arguments; copy up to min(N, `count`), // leave `count` -> N as zeroes else { constexpr size_t copy_size = std::min(N, count); size_t i = 0; ((i++ < copy_size ? (v[i - 1] = a, true) : false), ...); } } #ifdef __clang__ #pragma clang diagnostic pop #endif private: template constexpr void copy_impl(const FT &from, std::index_sequence packed) { ((v[pack] = from[pack]), ...); } public: // copy from C-style array, exact lengths only template constexpr qvec(const T2 (&array)[N]) { copy_impl(array, std::make_index_sequence()); } // copy from std::array, exact lengths only template constexpr qvec(const std::array &array) { copy_impl(array, std::make_index_sequence()); } /** * Casting from another vector type of the same length */ template constexpr qvec(const qvec &other) { copy_impl(other, std::make_index_sequence()); } private: template constexpr void copy_trunc_impl(const FT &from, std::index_sequence packed) { (( (pack < N) ? (v[pack] = ((pack < from.size() ? (from[pack]) : 0))) : (false) ), ...); } public: /** * Casting from another vector type of the same type but * different length */ template constexpr qvec(const qvec &other) { // truncates if `other` is longer than `this` // zero-fill if `other` is smaller than `this` copy_trunc_impl(other, std::make_index_sequence()); } /** * Extending a vector */ constexpr qvec(const qvec &other, T value) { std::copy(other.begin(), other.end(), v.begin()); v[N - 1] = value; } [[nodiscard]] constexpr size_t size() const { return N; } // Sort support [[nodiscard]] constexpr bool operator<(const qvec &other) const { return v < other.v; } [[nodiscard]] constexpr bool operator<=(const qvec &other) const { return v <= other.v; } [[nodiscard]] constexpr bool operator>(const qvec &other) const { return v > other.v; } [[nodiscard]] constexpr bool operator>=(const qvec &other) const { return v >= other.v; } [[nodiscard]] constexpr bool operator==(const qvec &other) const { return v == other.v; } [[nodiscard]] constexpr bool operator!=(const qvec &other) const { return v != other.v; } [[nodiscard]] constexpr const T &at(const size_t idx) const { assert(idx >= 0 && idx < N); return v[idx]; } [[nodiscard]] constexpr T &at(const size_t idx) { assert(idx >= 0 && idx < N); return v[idx]; } [[nodiscard]] constexpr const T &operator[](const size_t idx) const { return at(idx); } [[nodiscard]] constexpr T &operator[](const size_t idx) { return at(idx); } private: // OUT = op THIS[N] template constexpr auto utransform_impl(UnaryOperation func, std::index_sequence) const { using R = decltype(-T()); return qvec { func(at(pack))... }; } // OUT = THIS[N] op IN template constexpr auto transform_impl(BinaryOperation func, const InputType &b, std::index_sequence) const { using R = decltype(func(T(), InputType())); return qvec { func(at(pack), b)... }; } // OUT = THIS[N] op IN[N] template constexpr auto transformv_impl(BinaryOperation func, const InputType &b, std::index_sequence) const { using R = decltype(func(T(), InputType()[0])); return qvec { func(at(pack), b[pack])... }; } public: template constexpr auto utransform(UnaryOperation func) const { return utransform_impl(func, std::make_index_sequence()); } template constexpr auto transform(BinaryOperation func, const InputType &b) const { return transform_impl(func, b, std::make_index_sequence()); } template constexpr auto transformv(BinaryOperation func, const InputType &b) const { return transformv_impl(func, b, std::make_index_sequence()); } template [[nodiscard]] constexpr inline auto operator+(const qvec &other) const { return transformv(std::plus(), other); } template [[nodiscard]] constexpr inline auto operator-(const qvec &other) const { return transformv(std::minus(), other); } template [[nodiscard]] constexpr inline auto operator*(const S &scale) const { return transform(std::multiplies(), scale); } template [[nodiscard]] constexpr inline auto operator*(const qvec &scale) const { return transformv(std::multiplies(), scale); } template [[nodiscard]] constexpr inline auto operator/(const S &scale) const { return transform(std::divides(), scale); } template [[nodiscard]] constexpr inline auto operator/(const qvec &scale) const { return transformv(std::divides(), scale); } [[nodiscard]] constexpr inline auto operator-() const { return utransform(std::negate()); } template constexpr qvec operator+=(const qvec &other) { return *this = *this + other; } template constexpr qvec operator-=(const qvec &other) { return *this = *this - other; } template constexpr qvec operator*=(const S &scale) { return *this = *this * scale; } template constexpr qvec &operator*=(const qvec &other) { return *this = *this * other; } template constexpr qvec operator/=(const S &scale) { return *this = *this / scale; } template constexpr qvec operator/=(const qvec &other) { return *this = *this * other; } [[nodiscard]] constexpr qvec xyz() const { static_assert(N >= 3); return qvec(*this); } // stream support auto stream_data() { return std::tie(v); } // iterator support constexpr auto begin() { return v.begin(); } constexpr auto end() { return v.end(); } constexpr auto begin() const { return v.begin(); } constexpr auto end() const { return v.end(); } constexpr auto cbegin() const { return v.cbegin(); } constexpr auto cend() const { return v.cend(); } }; // Fmt support template struct fmt::formatter> { constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin()) { return ctx.end(); } template auto format(const qvec &p, FormatContext &ctx) -> decltype(ctx.out()) { for (size_t i = 0; i < N - 1; i++) { fmt::format_to(ctx.out(), "{}", p[i]); fmt::format_to(ctx.out(), " "); } return fmt::format_to(ctx.out(), "{}", p[N - 1]); } }; namespace qv { template [[nodiscard]] constexpr auto cross(const qvec &v1, const qvec &v2) { return qvec{ v1[1] * v2[2] - v1[2] * v2[1], v1[2] * v2[0] - v1[0] * v2[2], v1[0] * v2[1] - v1[1] * v2[0]}; } template constexpr auto dot_impl(const T &v1, const U &v2, std::index_sequence packed) { return ((v1[pack] * v2[pack]) + ...); } template> [[nodiscard]] constexpr L dot(const T &v1, const U &v2) { static_assert(std::size(T()) == std::size(U()), "Can't dot() with two differently-sized vectors"); constexpr size_t N = std::size(T()); return dot_impl(v1, v2, std::make_index_sequence()); } template [[nodiscard]] inline qvec floor(const qvec &v1) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::floor(v1[i]); } return res; } template [[nodiscard]] inline qvec ceil(const qvec &v1) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::ceil(v1[i]); } return res; } template [[nodiscard]] inline qvec pow(const qvec &v1, const qvec &v2) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::pow(v1[i], v2[i]); } return res; } template [[nodiscard]] constexpr T min(const qvec &v) { T res = std::numeric_limits::largest(); for (auto &c : v) { res = std::min(c, res); } return res; } template [[nodiscard]] constexpr T max(const qvec &v) { T res = std::numeric_limits::lowest(); for (auto &c : v) { res = std::max(c, res); } return res; } template [[nodiscard]] inline qvec abs(const qvec &v) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::abs(v[i]); } return res; } template [[nodiscard]] inline qvec min(const qvec &v1, const qvec &v2) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::min(v1[i], v2[i]); } return res; } template [[nodiscard]] inline qvec max(const qvec &v1, const qvec &v2) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = std::max(v1[i], v2[i]); } return res; } template [[nodiscard]] constexpr T length2(const qvec &v1) { return qv::dot(v1, v1); } template [[nodiscard]] inline T length(const qvec &v1) { return std::sqrt(length2(v1)); } template [[nodiscard]] inline T distance(const qvec &v1, const qvec &v2) { return length(v2 - v1); } template [[nodiscard]] constexpr T distance2(const qvec &v1, const qvec &v2) { return length2(v2 - v1); } template [[nodiscard]] inline qvec normalize(const qvec &v1, T &len) { len = length(v1); return len ? (v1 / len) : v1; } template [[nodiscard]] inline qvec normalize(const qvec &v1) { T len = length(v1); return len ? (v1 / len) : v1; } template inline T normalizeInPlace(qvec &v1) { T len = length(v1); if (len) { v1 /= len; } return len; } template [[nodiscard]] inline std::string to_string(const qvec &v1) { return fmt::format("{}", v1); } // explicit specialization, for reducing compile times template<> [[nodiscard]] std::string to_string(const qvec &v1); // explicit specialization, for reducing compile times template<> [[nodiscard]] std::string to_string(const qvec &v1); template [[nodiscard]] inline bool epsilonEqual(const T &v1, const T &v2, T epsilon) { return fabs(v1 - v2) <= epsilon; } template [[nodiscard]] inline bool epsilonEqual(const qvec &v1, const qvec &v2, T epsilon) { for (size_t i = 0; i < N; i++) { if (!epsilonEqual(v1[i], v2[i], epsilon)) return false; } return true; } template [[nodiscard]] inline bool epsilonEmpty(const qvec &v1, T epsilon) { return epsilonEqual({}, v1, epsilon); } template [[nodiscard]] inline bool gate(const T &v1, T epsilon) { return v1 <= epsilon; } template [[nodiscard]] inline bool gate(const qvec &v, T epsilon) { for (size_t i = 0; i < N; i++) { if (!gate(v[i], epsilon)) { return false; } } return true; } template [[nodiscard]] constexpr bool equalExact(const qvec &v1, const qvec &v2) { return v1 == v2; } template [[nodiscard]] constexpr bool emptyExact(const qvec &v1) { return equalExact({}, v1); } template [[nodiscard]] inline size_t indexOfLargestMagnitudeComponent(const qvec &v) { size_t largestIndex = 0; T largestMag = 0; for (size_t i = 0; i < N; ++i) { const T currentMag = std::fabs(v[i]); if (currentMag > largestMag) { largestMag = currentMag; largestIndex = i; } } return largestIndex; } template std::tuple, qvec> MakeTangentAndBitangentUnnormalized(const qvec &normal) { // 0, 1, or 2 const int axis = qv::indexOfLargestMagnitudeComponent(normal); const int otherAxisA = (axis + 1) % 3; const int otherAxisB = (axis + 2) % 3; // setup two other vectors that are perpendicular to each other qvec otherVecA{}; otherVecA[otherAxisA] = 1.0; qvec otherVecB{}; otherVecB[otherAxisB] = 1.0; auto tangent = qv::cross(normal, otherVecA); auto bitangent = qv::cross(normal, otherVecB); // We want `test` to point in the same direction as normal. // Swap the tangent bitangent if we got the direction wrong. qvec test = qv::cross(tangent, bitangent); if (qv::dot(test, normal) < 0) { std::swap(tangent, bitangent); } // debug test #ifdef PARANOID if (0) { auto n = qv::normalize(qv::cross(tangent, bitangent)); double d = qv::distance(n, normal); assert(d < 0.0001); } #endif return {tangent, bitangent}; } template [[nodiscard]] inline T TriangleArea(const qvec &v0, const qvec &v1, const qvec &v2) { return static_cast(0.5) * qv::length(qv::cross(v2 - v0, v1 - v0)); } template::value_type> [[nodiscard]] inline T PolyCentroid(Iter begin, Iter end) { using value_type = typename T::value_type; size_t num_points = end - begin; if (!num_points) return qvec(std::numeric_limits::quiet_NaN()); else if (num_points == 1) return *begin; else if (num_points == 2) return avg(*begin, *(begin + 1)); T poly_centroid{}; value_type poly_area = 0; const T &v0 = *begin; for (auto it = begin + 2; it != end; ++it) { const T &v1 = *(it - 1); const T &v2 = *it; const value_type triarea = TriangleArea(v0, v1, v2); const T tricentroid = avg(v0, v1, v2); poly_area += triarea; poly_centroid = poly_centroid + (tricentroid * triarea); } poly_centroid /= poly_area; return poly_centroid; } template::value_type, typename F = typename T::value_type> [[nodiscard]] inline F PolyArea(Iter begin, Iter end) { if ((end - begin) < 3) return static_cast(0); float poly_area = 0; const T &v0 = *begin; for (auto it = begin + 2; it != end; ++it) { const T &v1 = *(it - 1); const T &v2 = *it; poly_area += TriangleArea(v0, v1, v2); } return poly_area; } template [[nodiscard]] inline qvec Barycentric_FromPoint( const qvec &p, const qvec &t0, const qvec &t1, const qvec &t2) { const auto v0 = t1 - t0; const auto v1 = t2 - t0; const auto v2 = p - t0; T d00 = qv::dot(v0, v0); T d01 = qv::dot(v0, v1); T d11 = qv::dot(v1, v1); T d20 = qv::dot(v2, v0); T d21 = qv::dot(v2, v1); T invDenom = (d00 * d11 - d01 * d01); invDenom = 1.0 / invDenom; qvec res; res[1] = (d11 * d20 - d01 * d21) * invDenom; res[2] = (d00 * d21 - d01 * d20) * invDenom; res[0] = 1.0 - res[1] - res[2]; return res; } // from global illumination total compendium p. 12 template [[nodiscard]] inline qvec Barycentric_Random(T r1, T r2) { qvec res; res[0] = 1.0 - sqrt(r1); res[1] = r2 * sqrt(r1); res[2] = 1.0 - res[0] - res[1]; return res; } /// Evaluates the given barycentric coord for the given triangle template [[nodiscard]] constexpr qvec Barycentric_ToPoint( const qvec &bary, const qvec &t0, const qvec &t1, const qvec &t2) { return (t0 * bary[0]) + (t1 * bary[1]) + (t2 * bary[2]); } // Snap vector to nearest axial component template [[nodiscard]] inline qvec Snap(qvec normal, const T &epsilon = NORMAL_EPSILON) { for (auto &v : normal) { if (fabs(v - 1) < epsilon) { normal = {}; v = 1; break; } if (fabs(v - -1) < epsilon) { normal = {}; v = -1; break; } } return normal; } template inline qvec mangle_from_vec(const qvec &v) { static constexpr qvec up(0, 0, 1); static constexpr qvec east(1, 0, 0); static constexpr qvec north(0, 1, 0); // get rotation about Z axis T x = qv::dot(east, v); T y = qv::dot(north, v); T theta = atan2(y, x); // get angle away from Z axis T cosangleFromUp = qv::dot(up, v); cosangleFromUp = std::min(std::max(static_cast(-1.0), cosangleFromUp), static_cast(1.0)); T radiansFromUp = acosf(cosangleFromUp); return qvec{theta, -(radiansFromUp - Q_PI / 2.0), 0} * static_cast(180.0 / Q_PI); } /* detect colors with components in 0-1 and scale them to 0-255 */ template constexpr qvec normalize_color_format(const qvec &color) { if (color[0] >= 0 && color[0] <= 1 && color[1] >= 0 && color[1] <= 1 && color[2] >= 0 && color[2] <= 1) { return color * 255; } return color; } }; // namespace qv using qvec2f = qvec; using qvec3f = qvec; using qvec4f = qvec; using qvec2d = qvec; using qvec3d = qvec; using qvec4d = qvec; using qvec2i = qvec; using qvec3i = qvec; using qvec3s = qvec; using qvec3b = qvec; using qvec4b = qvec; template class qplane3 { public: qvec normal; T dist; constexpr qplane3() = default; constexpr qplane3(const qvec &normal, const T &dist) : normal(normal), dist(dist) { } // convert from plane of a different type template explicit constexpr qplane3(const qplane3 &plane) : qplane3(plane.normal, static_cast(plane.dist)) { } private: auto as_tuple() const { return std::tie(normal, dist); } public: // Sort support [[nodiscard]] constexpr bool operator<(const qplane3 &other) const { return as_tuple() < other.as_tuple(); } [[nodiscard]] constexpr bool operator<=(const qplane3 &other) const { return as_tuple() <= other.as_tuple(); } [[nodiscard]] constexpr bool operator>(const qplane3 &other) const { return as_tuple() > other.as_tuple(); } [[nodiscard]] constexpr bool operator>=(const qplane3 &other) const { return as_tuple() >= other.as_tuple(); } [[nodiscard]] constexpr bool operator==(const qplane3 &other) const { return as_tuple() == other.as_tuple(); } [[nodiscard]] constexpr bool operator!=(const qplane3 &other) const { return as_tuple() != other.as_tuple(); } [[nodiscard]] constexpr const qvec vec4() const { return qvec(normal[0], normal[1], normal[2], dist); } [[nodiscard]] constexpr qplane3 operator-() const { return {-normal, -dist}; } // generic case template [[nodiscard]] inline F distance_to(const qvec &point) const { return qv::dot(point, normal) - dist; } // stream support void stream_write(std::ostream &s) const { s <= std::tie(normal, dist); } void stream_read(std::istream &s) { s >= std::tie(normal, dist); } }; // Fmt support template struct fmt::formatter> : formatter> { template auto format(const qplane3 &p, FormatContext &ctx) -> decltype(ctx.out()) { fmt::format_to(ctx.out(), "{{normal: "); fmt::formatter>::format(p.normal, ctx); fmt::format_to(ctx.out(), ", dist: {}}}", p.dist); return ctx.out(); } }; using qplane3f = qplane3; using qplane3d = qplane3; namespace qv { template [[nodiscard]] bool epsilonEqual( const qplane3 &p1, const qplane3 &p2, T normalEpsilon = NORMAL_EPSILON, T distEpsilon = DIST_EPSILON) { return epsilonEqual(p1.normal, p2.normal, normalEpsilon) && epsilonEqual(p1.dist, p2.dist, distEpsilon); } } // namespace qv /** * Row x Col matrix of T. */ template class qmat { public: /** * Column-major order. [ (row0,col0), (row1,col0), .. ] */ std::array m_values; public: /** * Identity matrix if square, otherwise fill with 0 */ constexpr qmat() : m_values({}) { if constexpr (NRow == NCol) { // identity matrix for (size_t i = 0; i < NCol; i++) { this->at(i, i) = 1; } } } /** * Fill with a value */ inline qmat(const T &val) { m_values.fill(val); } // copy constructor constexpr qmat(const qmat &other) : m_values(other.m_values) { } /** * Casting from another matrix type of the same size */ template explicit constexpr qmat(const qmat &other) { for (size_t i = 0; i < NRow * NCol; i++) this->m_values[i] = static_cast(other.m_values[i]); } // initializer list, column-major order constexpr qmat(std::initializer_list list) { assert(list.size() == NRow * NCol); std::copy(list.begin(), list.end(), m_values.begin()); } // static factory, row-major order static qmat row_major(std::initializer_list list) { assert(list.size() == NRow * NCol); qmat result; for (size_t i = 0; i < NRow; i++) { // for each row for (size_t j = 0; j < NCol; j++) { // for each col result.at(i, j) = *(list.begin() + (i * NCol + j)); } } return result; } // Sort support [[nodiscard]] constexpr bool operator<(const qmat &other) const { return m_values < other.m_values; } [[nodiscard]] constexpr bool operator<=(const qmat &other) const { return m_values <= other.m_values; } [[nodiscard]] constexpr bool operator>(const qmat &other) const { return m_values > other.m_values; } [[nodiscard]] constexpr bool operator>=(const qmat &other) const { return m_values >= other.m_values; } [[nodiscard]] constexpr bool operator==(const qmat &other) const { return m_values == other.m_values; } [[nodiscard]] constexpr bool operator!=(const qmat &other) const { return m_values != other.m_values; } // access to elements [[nodiscard]] constexpr T &at(size_t row, size_t col) { assert(row >= 0 && row < NRow); assert(col >= 0 && col < NCol); return m_values[col * NRow + row]; } [[nodiscard]] constexpr T at(size_t row, size_t col) const { assert(row >= 0 && row < NRow); assert(col >= 0 && col < NCol); return m_values[col * NRow + row]; } // access row [[nodiscard]] inline qvec row(size_t row) const { assert(row >= 0 && row < NRow); qvec v; for (size_t i = 0; i < NCol; i++) { v[i] = at(row, i); } return v; } constexpr void set_row(size_t row, const qvec &values) { for (size_t i = 0; i < NCol; i++) { at(row, i) = values[i]; } } [[nodiscard]] constexpr const qvec &col(size_t col) const { assert(col >= 0 && col < NCol); return reinterpret_cast &>(m_values[col * NRow]); } inline void set_col(size_t col, const qvec &values) { reinterpret_cast &>(m_values[col * NRow]) = values; } // multiplication by a vector [[nodiscard]] constexpr qvec operator*(const qvec &vec) const { qvec res{}; for (size_t i = 0; i < NRow; i++) { // for each row for (size_t j = 0; j < NCol; j++) { // for each col res[i] += this->at(i, j) * vec[j]; } } return res; } // multiplication by a matrix template [[nodiscard]] constexpr qmat operator*(const qmat &other) const { qmat res; for (size_t i = 0; i < NRow; i++) { for (size_t j = 0; j < PCol; j++) { T val = 0; for (size_t k = 0; k < NCol; k++) { val += this->at(i, k) * other.at(k, j); } res.at(i, j) = val; } } return res; } // multiplication by a scalar [[nodiscard]] constexpr qmat operator*(const T &scalar) const { qmat res(*this); for (size_t i = 0; i < NRow * NCol; i++) { res.m_values[i] *= scalar; } return res; } [[nodiscard]] constexpr qmat transpose() const { qmat res; for (size_t i = 0; i < NRow; i++) { for (size_t j = 0; j < NCol; j++) { res.at(j, i) = at(i, j); } } return res; } }; // Fmt support template struct fmt::formatter> : formatter> { template auto format(const qmat &p, FormatContext &ctx) -> decltype(ctx.out()) { for (size_t i = 0; i < NRow; i++) { fmt::format_to(ctx.out(), "[ "); fmt::formatter>::format(p.row(i), ctx); fmt::format_to(ctx.out(), " ]"); if (i != NRow - 1) { fmt::format_to(ctx.out(), "\n"); } } return ctx.out(); } }; using qmat2x2f = qmat; using qmat3x3f = qmat; using qmat4x4f = qmat; using qmat2x2d = qmat; using qmat3x3d = qmat; using qmat4x4d = qmat; namespace qv { /** * These return a matrix filled with NaN if there is no inverse. */ [[nodiscard]] qmat4x4f inverse(const qmat4x4f &input); [[nodiscard]] qmat4x4d inverse(const qmat4x4d &input); [[nodiscard]] qmat2x2f inverse(const qmat2x2f &input); [[nodiscard]] qmat3x3f inverse(const qmat3x3f &input); }; // namespace qv // returns the normalized direction from `start` to `stop` in the `dir` param // returns the distance from `start` to `stop` template inline double GetDir(const Tstart &start, const Tstop &stop, Tdir &dir) { return qv::normalizeInPlace(dir = (stop - start)); } // Stores a normal, tangent and bitangent struct face_normal_t { qvec3f normal, tangent, bitangent; }; qmat3x3d RotateAboutX(double radians); qmat3x3d RotateAboutY(double radians); qmat3x3d RotateAboutZ(double radians); qmat3x3f RotateFromUpToSurfaceNormal(const qvec3f &surfaceNormal); // Returns (0 0 0) if we couldn't determine the normal qvec3f FaceNormal(std::vector points); std::pair MakeInwardFacingEdgePlane(const qvec3f &v0, const qvec3f &v1, const qvec3f &faceNormal); std::vector MakeInwardFacingEdgePlanes(const std::vector &points); bool EdgePlanes_PointInside(const std::vector &edgeplanes, const qvec3f &point); float EdgePlanes_PointInsideDist(const std::vector &edgeplanes, const qvec3f &point); qvec4f MakePlane(const qvec3f &normal, const qvec3f &point); float DistAbovePlane(const qvec4f &plane, const qvec3f &point); qvec3f ProjectPointOntoPlane(const qvec4f &plane, const qvec3f &point); qvec4f PolyPlane(const std::vector &points); /// Returns the index of the polygon edge, and the closest point on that edge, to the given point std::pair ClosestPointOnPolyBoundary(const std::vector &poly, const qvec3f &point); /// Returns `true` and the interpolated normal if `point` is in the polygon, otherwise returns false. std::pair InterpolateNormal( const std::vector &points, const std::vector &normals, const qvec3f &point); std::pair InterpolateNormal( const std::vector &points, const std::vector &normals, const qvec3f &point); std::vector ShrinkPoly(const std::vector &poly, const float amount); /// Returns (front part, back part) std::pair, std::vector> ClipPoly(const std::vector &poly, const qvec4f &plane); class poly_random_point_state_t { public: std::vector points; std::vector triareas; std::vector triareas_cdf; }; poly_random_point_state_t PolyRandomPoint_Setup(const std::vector &points); qvec3f PolyRandomPoint(const poly_random_point_state_t &state, float r1, float r2, float r3); /// projects p onto the vw line. /// returns 0 for p==v, 1 for p==w float FractionOfLine(const qvec3f &v, const qvec3f &w, const qvec3f &p); /** * Distance from `p` to the line v<->w (extending infinitely in either direction) */ float DistToLine(const qvec3f &v, const qvec3f &w, const qvec3f &p); qvec3f ClosestPointOnLine(const qvec3f &v, const qvec3f &w, const qvec3f &p); /** * Distance from `p` to the line segment v<->w. * i.e., 0 if `p` is between v and w. */ float DistToLineSegment(const qvec3f &v, const qvec3f &w, const qvec3f &p); qvec3f ClosestPointOnLineSegment(const qvec3f &v, const qvec3f &w, const qvec3f &p); float SignedDegreesBetweenUnitVectors(const qvec3f &start, const qvec3f &end, const qvec3f &normal); enum class concavity_t { Coplanar, Concave, Convex }; concavity_t FacePairConcavity( const qvec3f &face1Center, const qvec3f &face1Normal, const qvec3f &face2Center, const qvec3f &face2Normal); // Returns weights for f(0,0), f(1,0), f(0,1), f(1,1) // from: https://en.wikipedia.org/wiki/Bilinear_interpolation#Unit_Square qvec4f bilinearWeights(const float x, const float y); // This uses a coordinate system where the pixel centers are on integer coords. // e.g. the corners of a 3x3 pixel bitmap are at (-0.5, -0.5) and (2.5, 2.5). std::array, 4> bilinearWeightsAndCoords(qvec2f pos, const qvec2i &size); template V bilinearInterpolate(const V &f00, const V &f10, const V &f01, const V &f11, const float x, const float y) { qvec4f weights = bilinearWeights(x, y); const V fxy = f00 * weights[0] + f10 * weights[1] + f01 * weights[2] + f11 * weights[3]; return fxy; } template std::vector PointsAlongLine(const V &start, const V &end, const float step) { const V linesegment = end - start; const float len = qv::length(linesegment); if (len == 0) return {}; std::vector result; const int stepCount = static_cast(len / step); result.reserve(stepCount + 1); const V dir = linesegment / len; for (int i = 0; i <= stepCount; i++) { result.push_back(start + (dir * (step * i))); } return result; } bool LinesOverlap(const qvec3f &p0, const qvec3f &p1, const qvec3f &q0, const qvec3f &q1, const double &on_epsilon = DEFAULT_ON_EPSILON); template struct twosided { T front, back; // 0 is front, 1 is back constexpr T &operator[](const int32_t &i) { switch (i) { case 0: return front; case 1: return back; } throw std::exception(); } // 0 is front, 1 is back constexpr const T &operator[](const int32_t &i) const { switch (i) { case 0: return front; case 1: return back; } throw std::exception(); } // iterator support T *begin() { return &front; } T *end() { return (&back) + 1; } const T *begin() const { return &front; } const T *end() const { return (&back) + 1; } // swap the front and back values constexpr void swap() { std::swap(front, back); } }; namespace qv { template inline qvec vec_from_mangle(const qvec &m) { const qvec mRadians = m * static_cast(Q_PI / 180.0); const qmat3x3d rotations = RotateAboutZ(mRadians[0]) * RotateAboutY(-mRadians[1]); return {rotations * qvec3d(1, 0, 0)}; } } // namespace qv // for Catch2 template std::ostream &operator<<(std::ostream &os, const qvec &v) { return os << qv::to_string(v); }