/* 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 "common/mathlib.hh" #include "common/cmdlib.hh" template class qvec { protected: std::array v; template friend class qvec; public: using value_type = T; inline 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 = min(N, count); size_t i = 0; ((i++ < copy_size ? (v[i - 1] = a, true) : false), ...); } } #ifdef __clang__ #pragma clang diagnostic pop #endif // copy from C-style array, exact lengths only template constexpr qvec(const T2 (&array)[N]) { for (size_t i = 0; i < N; i++) v[i] = static_cast(array[i]); } // copy from std::array, exact lengths only template constexpr qvec(const std::array &array) { for (size_t i = 0; i < N; i++) v[i] = static_cast(array[i]); } /** * Casting from another vector type of the same length */ template constexpr qvec(const qvec &other) { for (size_t i = 0; i < N; i++) v[i] = static_cast(other[i]); } /** * Casting from another vector type of the same type but * different length */ template constexpr qvec(const qvec &other) { constexpr size_t minSize = min(N, N2); // truncates if `other` is longer than `this` for (size_t i = 0; i < minSize; i++) v[i] = other[i]; // zero-fill if `other` is smaller than `this` if constexpr (N2 < N) { for (size_t i = minSize; i < N; i++) v[i] = 0; } } /** * 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); } template constexpr void operator+=(const qvec &other) { for (size_t i = 0; i < N; i++) v[i] += other.v[i]; } template constexpr void operator-=(const qvec &other) { for (size_t i = 0; i < N; i++) v[i] -= other.v[i]; } constexpr void operator*=(const T &scale) { for (size_t i = 0; i < N; i++) v[i] *= scale; } constexpr void operator/=(const T &scale) { for (size_t i = 0; i < N; i++) v[i] /= scale; } template [[nodiscard]] constexpr qvec operator+(const qvec &other) const { qvec res(*this); res += other; return res; } template [[nodiscard]] constexpr qvec operator-(const qvec &other) const { qvec res(*this); res -= other; return res; } [[nodiscard]] constexpr qvec operator*(const T &scale) const { qvec res(*this); res *= scale; return res; } [[nodiscard]] constexpr qvec operator/(const T &scale) const { qvec res(*this); res /= scale; return res; } [[nodiscard]] constexpr qvec operator-() const { qvec res(*this); res *= -1; return res; } [[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(); } // for Google Test friend std::ostream& operator<<(std::ostream& os, const qvec& v) { return os << fmt::format("{}", v); } }; namespace qv { template [[nodiscard]] qvec 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 [[nodiscard]] T dot(const qvec &v1, const qvec &v2) { T result = 0; for (size_t i = 0; i < N; i++) { result += v1[i] * v2[i]; } return result; } template [[nodiscard]] 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]] 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]] T min(const qvec &v) { T res = std::numeric_limits::largest(); for (auto &c : v) { res = ::min(c, res); } return res; } template [[nodiscard]] T max(const qvec &v) { T res = std::numeric_limits::lowest(); for (auto &c : v) { res = ::max(c, res); } return res; } template [[nodiscard]] qvec min(const qvec &v1, const qvec &v2) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = ::min(v1[i], v2[i]); } return res; } template [[nodiscard]] qvec max(const qvec &v1, const qvec &v2) { qvec res; for (size_t i = 0; i < N; i++) { res[i] = ::max(v1[i], v2[i]); } return res; } template [[nodiscard]] T length2(const qvec &v1) { T len2 = 0; for (size_t i = 0; i < N; i++) { len2 += (v1[i] * v1[i]); } return len2; } template [[nodiscard]] T length(const qvec &v1) { return std::sqrt(length2(v1)); } template [[nodiscard]] qvec normalize(const qvec &v1) { return v1 / length(v1); } template [[nodiscard]] T distance(const qvec &v1, const qvec &v2) { return length(v2 - v1); } template [[nodiscard]] inline std::string to_string(const qvec &v1) { return fmt::format("{}", v1); } template [[nodiscard]] bool epsilonEqual(const qvec &v1, const qvec &v2, T epsilon) { for (size_t i = 0; i < N; i++) { T diff = v1[i] - v2[i]; if (fabs(diff) > epsilon) return false; } return true; } template [[nodiscard]] bool epsilonEmpty(const qvec &v1, T epsilon) { for (size_t i = 0; i < N; i++) { if (fabs(v1[i]) > epsilon) return false; } return true; } template [[nodiscard]] bool equalExact(const qvec &v1, const qvec &v2) { for (size_t i = 0; i < N; i++) { if (v1[i] != v2[i]) return false; } return true; } template [[nodiscard]] bool emptyExact(const qvec &v1) { for (size_t i = 0; i < N; i++) { if (v1[i]) return false; } return true; } template [[nodiscard]] 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 [[nodiscard]] 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]] 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]] F PolyArea(Iter begin, Iter end) { Q_assert((end - begin) >= 3); 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]] 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]] 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]] 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]); } }; // 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; template class qplane3 { private: qvec m_normal; T m_dist; public: inline qplane3() = default; constexpr qplane3(const qvec &normal, const T &dist) : m_normal(normal), m_dist(dist) { } template constexpr qplane3(const qplane3 &plane) : qplane3(plane.normal(), plane.dist()) { } [[nodiscard]] inline T distAbove(const qvec &pt) const { return qv::dot(pt, m_normal) - m_dist; } [[nodiscard]] constexpr const qvec &normal() const { return m_normal; } [[nodiscard]] constexpr const T dist() const { return m_dist; } [[nodiscard]] constexpr const qvec vec4() const { return qvec(m_normal[0], m_normal[1], m_normal[2], m_dist); } }; using qplane3f = qplane3; using qplane3d = qplane3; /** * M row, N column matrix. */ 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 (M == N) { // identity matrix for (size_t i = 0; i < N; 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 constexpr qmat(const qmat &other) { for (size_t i = 0; i < M * N; 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() == M * N); std::copy(list.begin(), list.end(), m_values.begin()); } 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 < M); assert(col >= 0 && col < N); return m_values[col * M + row]; } [[nodiscard]] constexpr T at(size_t row, size_t col) const { assert(row >= 0 && row < M); assert(col >= 0 && col < N); return m_values[col * M + row]; } // hacky accessor for mat[col][row] access [[nodiscard]] constexpr const T *operator[](size_t col) const { assert(col >= 0 && col < N); return &m_values[col * M]; } [[nodiscard]] constexpr T *operator[](size_t col) { assert(col >= 0 && col < N); return &m_values[col * M]; } // multiplication by a vector [[nodiscard]] constexpr qvec operator*(const qvec &vec) const { qvec res{}; for (size_t i = 0; i < M; i++) { // for each row for (size_t j = 0; j < N; 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 < M; i++) { for (size_t j = 0; j < P; j++) { T val = 0; for (size_t k = 0; k < N; 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 < M * N; i++) { res.m_values[i] *= scalar; } return res; } }; using qmat2x2f = qmat<2, 2, float>; using qmat3x3f = qmat<3, 3, float>; using qmat4x4f = qmat<4, 4, float>; using qmat2x2d = qmat<2, 2, double>; using qmat3x3d = qmat<3, 3, double>; using qmat4x4d = qmat<4, 4, double>; 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); }; // namespace qv template struct fmt::formatter> : formatter { template auto format(const qvec &p, FormatContext &ctx) -> decltype(ctx.out()) { for (size_t i = 0; i < N - 1; i++) { formatter::format(p[i], ctx); format_to(ctx.out(), " "); } return formatter::format(p[N - 1], ctx); } }; // "vec3" type. legacy; eventually will be replaced entirely using vec3_t = vec_t[3]; struct plane_t { qvec3d normal; vec_t dist; }; extern const vec3_t vec3_origin; template constexpr bool VectorCompare(const T1 &v1, const T2 &v2, vec_t epsilon) { for (size_t i = 0; i < std::size(v1); i++) if (fabs(v1[i] - v2[i]) > epsilon) return false; return true; } template constexpr void CrossProduct(const T &v1, const T2 &v2, T3 &cross) { cross[0] = v1[1] * v2[2] - v1[2] * v2[1]; cross[1] = v1[2] * v2[0] - v1[0] * v2[2]; cross[2] = v1[0] * v2[1] - v1[1] * v2[0]; } template constexpr vec_t DotProduct(const Tx &x, const Ty &y) { return x[0] * y[0] + x[1] * y[1] + x[2] * y[2]; } template constexpr void VectorSubtract(const Tx &x, const Ty &y, Tout &out) { out[0] = x[0] - y[0]; out[1] = x[1] - y[1]; out[2] = x[2] - y[2]; } template constexpr void VectorAdd(const Tx &x, const Ty &y, Tout &out) { out[0] = x[0] + y[0]; out[1] = x[1] + y[1]; out[2] = x[2] + y[2]; } template constexpr void VectorCopy(const TFrom &in, TTo &out) { out[0] = in[0]; out[1] = in[1]; out[2] = in[2]; } template constexpr void VectorScale(const TFrom &v, TScale scale, TTo &out) { out[0] = v[0] * scale; out[1] = v[1] * scale; out[2] = v[2] * scale; } template constexpr void VectorInverse(T &v) { v[0] = -v[0]; v[1] = -v[1]; v[2] = -v[2]; } template constexpr void VectorSet(T &out, vec_t x, vec_t y, vec_t z) { out[0] = x; out[1] = y; out[2] = z; } template constexpr void VectorClear(T &out) { out[0] = 0; out[1] = 0; out[2] = 0; } plane_t FlipPlane(plane_t input); template constexpr void VectorMA(const Ta &va, vec_t scale, const Tb &vb, Tc &vc) { vc[0] = va[0] + scale * vb[0]; vc[1] = va[1] + scale * vb[1]; vc[2] = va[2] + scale * vb[2]; } template constexpr vec_t VectorLengthSq(const T &v) { vec_t length = 0; for (int i = 0; i < 3; i++) length += v[i] * v[i]; return length; } template inline vec_t VectorLength(const T &v) { vec_t length = VectorLengthSq(v); length = sqrt(length); return length; } template inline vec_t VectorNormalize(T &v) { vec_t length = 0; for (size_t i = 0; i < 3; i++) length += v[i] * v[i]; length = sqrt(length); if (length == 0) return 0; for (size_t i = 0; i < 3; i++) v[i] /= (vec_t)length; return (vec_t)length; } // returns the normalized direction from `start` to `stop` in the `dir` param // returns the distance from `start` to `stop` template inline vec_t GetDir(const Tstart &start, const Tstop &stop, Tdir &dir) { VectorSubtract(stop, start, dir); return VectorNormalize(dir); } // 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 GLM_FaceNormal(std::vector points); std::pair GLM_MakeInwardFacingEdgePlane(const qvec3f &v0, const qvec3f &v1, const qvec3f &faceNormal); std::vector GLM_MakeInwardFacingEdgePlanes(const std::vector &points); bool GLM_EdgePlanes_PointInside(const std::vector &edgeplanes, const qvec3f &point); float GLM_EdgePlanes_PointInsideDist(const std::vector &edgeplanes, const qvec3f &point); qvec4f GLM_MakePlane(const qvec3f &normal, const qvec3f &point); float GLM_DistAbovePlane(const qvec4f &plane, const qvec3f &point); qvec3f GLM_ProjectPointOntoPlane(const qvec4f &plane, const qvec3f &point); qvec4f GLM_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 GLM_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 GLM_InterpolateNormal( const std::vector &points, const std::vector &normals, const qvec3f &point); std::pair GLM_InterpolateNormal( const std::vector &points, const std::vector &normals, const qvec3f &point); std::vector GLM_ShrinkPoly(const std::vector &poly, const float amount); /// Returns (front part, back part) std::pair, std::vector> GLM_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 GLM_PolyRandomPoint_Setup(const std::vector &points); qvec3f GLM_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 inline qvec4f bilinearWeights(const float x, const float y) { Q_assert(x >= 0.0f); Q_assert(x <= 1.0f); Q_assert(y >= 0.0f); Q_assert(y <= 1.0f); return qvec4f((1.0f - x) * (1.0f - y), x * (1.0f - y), (1.0f - x) * y, x * 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). inline std::array, 4> bilinearWeightsAndCoords(qvec2f pos, const qvec2i &size) { Q_assert(pos[0] >= -0.5f && pos[0] <= (size[0] - 0.5f)); Q_assert(pos[1] >= -0.5f && pos[1] <= (size[1] - 0.5f)); // Handle extrapolation. for (int i = 0; i < 2; i++) { if (pos[i] < 0) pos[i] = 0; if (pos[i] > (size[i] - 1)) pos[i] = (size[i] - 1); } Q_assert(pos[0] >= 0.f && pos[0] <= (size[0] - 1)); Q_assert(pos[1] >= 0.f && pos[1] <= (size[1] - 1)); qvec2i integerPart{static_cast(qv::floor(pos)[0]), static_cast(qv::floor(pos)[1])}; qvec2f fractionalPart(pos - qv::floor(pos)); // ensure integerPart + (1, 1) is still in bounds for (int i = 0; i < 2; i++) { if (fractionalPart[i] == 0.0f && integerPart[i] > 0) { integerPart[i] -= 1; fractionalPart[i] = 1.0f; } } Q_assert(integerPart[0] + 1 < size[0]); Q_assert(integerPart[1] + 1 < size[1]); Q_assert(qvec2f(integerPart) + fractionalPart == pos); // f(0,0), f(1,0), f(0,1), f(1,1) const qvec4f weights = bilinearWeights(fractionalPart[0], fractionalPart[1]); std::array, 4> result; for (int i = 0; i < 4; i++) { const float weight = weights[i]; qvec2i pos(integerPart); if ((i % 2) == 1) pos[0] += 1; if (i >= 2) pos[1] += 1; Q_assert(pos[0] >= 0); Q_assert(pos[0] < size[0]); Q_assert(pos[1] >= 0); Q_assert(pos[1] < size[1]); result[i] = std::make_pair(pos, weight); } return result; } 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 vec_t &on_epsilon = DEFAULT_ON_EPSILON);