// L============================================================================= // L This software is distributed under the MIT license. // L Copyright 2021 Péter Kardos // L============================================================================= #pragma once #include "Vector.hpp" #include #include #include namespace mathter { //------------------------------------------------------------------------------ // Shapes //------------------------------------------------------------------------------ template class Hyperplane; template class Line { using VectorT = Vector; public: /// Does not zero-initialize members. Line() = default; /// Construct a line through in given . /// Any point in 3D space. /// Must be normalized. Line(const VectorT& base, const VectorT& direction) : direction(direction), base(base) { assert(IsNormalized(direction)); } /// Constructs a line through both points. /// Base of the line. /// Specifies direction only. static Line Through(const VectorT& point1, const VectorT& point2) { return Line(point1, SafeNormalize(point2 - point1)); } /// A 2D plane and line are equivalent, converts representation. Only for 2D. Line(const Hyperplane& plane); /// Return the signed direction of the line (as given in constructor). VectorT Direction() const { return direction; } /// Returns the base point or point1 as given in constructor. VectorT Base() const { return base; } /// Returns the point at distance from the base point along direction. VectorT PointAt(T param) const { return base + param * direction; } public: VectorT direction, base; }; template class LineSegment { using VectorT = Vector; public: LineSegment() : point1(0), point2(0) { point2(0) = 1; } LineSegment(const VectorT& base, const VectorT& direction, T length) { point1 = base; point2 = base + direction * length; } LineSegment(const VectorT& point1, const VectorT& point2) : point1(point1), point2(point2) {} T Length() const { return mathter::Length(point2 - point1); } VectorT Direction() const { return Normalize(point2 - point1); } VectorT Start() const { return point1; } VectorT End() const { return point2; } VectorT Interpol(T t) const { return t * point2 + (T(1) - t) * point1; } mathter::Line Line() const { return mathter::Line{ point1, Direction() }; } public: VectorT point1, point2; }; template class Ray : protected Line { public: // Inhreitance is protected to deny casting. // Casting is bad 'cause we don't want to implicit cast a Ray to a Line and Intersect() it with a plane. using Line::Line; using Line::Through; using Line::Direction; using Line::Base; using Line::PointAt; using Line::direction; using Line::base; mathter::Line Line() const { return static_cast>(*this); } }; template class Hyperplane { using VectorT = Vector; public: Hyperplane() : normal(0), scalar(0) { normal(0) = 1; } Hyperplane(const VectorT& base, const VectorT& normal) : normal(normal) { assert(IsNormalized(normal)); scalar = Dot(normal, base); } Hyperplane(const VectorT& normal, T scalar) : normal(normal), scalar(scalar) { assert(IsNormalized(normal)); } Hyperplane(const Line& line) { static_assert(Dim == 2, "Plane dimension must be two, which is a line."); normal = { -line.Direction()(1), line.Direction()(0) }; scalar = Dot(normal, line.Base()); } const VectorT& Normal() const { return normal; } T Scalar() const { return scalar; } template T Distance(const Vector& point) { return Dot(point, normal) - scalar; } private: VectorT normal; T scalar; }; template Line::Line(const Hyperplane& plane) { static_assert(Dim == 2, "Line dimension must be two, since it a plane in 2 dimensional space."); // Intersect plane's line with line through origo perpendicular to plane to find suitable base T a = plane.Normal()(0); T b = plane.Normal()(1); T d = plane.Scalar(); T div = (a * a + b * b); base = { a * d / div, b * d / div }; direction = { b, -a }; } template class Triangle3D { public: Triangle3D() = default; Triangle3D(const Vector& a, const Vector& b, const Vector& c) : a(a), b(b), c(c) {} Vector a, b, c; // Corners of the traingle. }; //------------------------------------------------------------------------------ // Intersections //------------------------------------------------------------------------------ template class Intersection; template auto Intersect(const T& t, const U& u) { return Intersection(t, u); } // Plane-line intersection template class Intersection, Line> { protected: using PlaneT = Hyperplane; using LineT = Line; using VectorT = Vector; public: Intersection(const PlaneT& plane, const LineT& line); bool Intersecting() const { return !std::isinf(param); } VectorT Point() const { return line.PointAt(param); } T LineParameter() const { return param; } private: LineT line; T param; }; template class Intersection, Hyperplane> : public Intersection, Line> { using PlaneT = Hyperplane; using LineT = Line; public: Intersection(const LineT& line, const PlaneT& plane) : Intersection, Line>(plane, line) {} }; // Plane-line segment intersection template class Intersection, LineSegment> { using PlaneT = Hyperplane; using LineT = LineSegment; using VectorT = Vector; public: Intersection(const PlaneT& plane, const LineT& line) { lineSegment = line; auto intersection = Intersect(plane, line.Line()); param = intersection.LineParameter() / line.Length(); } bool Intersecting() const { return T(0) <= param && param <= T(1); } VectorT Point() const { return lineSegment.Interpol(param); } T InterpolParameter() const { return param; } T LineParameter() const { return param * lineSegment.Length(); } private: LineT lineSegment; T param; }; template class Intersection, Hyperplane> : public Intersection, LineSegment> { using PlaneT = Hyperplane; using LineT = LineSegment; public: Intersection(const LineT& line, const PlaneT& plane) : Intersection, LineSegment>(plane, line) {} }; template Intersection, Line>::Intersection(const PlaneT& plane, const LineT& line) { this->line = line; // We have to solve the system of linear equations for x,y,z,t // |d | |a b c 0| |x| // |px| |1 0 0 p| |y| // |py| = |0 1 0 q| * |z| // |pz| |0 0 1 r| |t| // b = A * x // where [px,py,pz] + t*[p,q,r] = [x,y,z] is the line's equation // and ax + by + cz + d = 0 is the plane's equation Vector b; Vector A_inv_t; // Fill up 'b' b = -plane.Scalar() | line.Base(); // Fill up 'A_inv_t', which is the last line of A^-1, used to calculate 't' A_inv_t = 1 | plane.Normal(); // Compute result of the equation T scaler = Dot(line.Direction(), plane.Normal()); T x_t = Dot(A_inv_t, b); T t = x_t / scaler; param = -t; } // 2D line intersection // with lines template class Intersection, Line> { using LineT = Line; public: Intersection(const LineT& l1, const LineT& l2) { line2 = l2; auto intersection = Intersect(Hyperplane(l1), l2); param2 = intersection.LineParameter(); param1 = std::isinf(param2) ? std::numeric_limits::infinity() : Length(intersection.Point() - l1.Base()); } bool Intersecting() const { return !std::isinf(param1); } T LineParameter1() const { return param1; } T LineParameter2() const { return param2; } Vector Point() const { return line2.PointAt(param2); } private: T param1, param2; LineT line2; }; // with hyperplanes template class Intersection, Hyperplane> : public Intersection, Line> { public: Intersection(const Hyperplane& p1, const Hyperplane& p2) : Intersection, Line>(p1, p2) {} }; // line segments template class Intersection, LineSegment> { public: Intersection(const LineSegment& l1, const LineSegment& l2) { lineSegment1 = l1; lineSegment2 = l2; auto intersection = Intersect(l1.Line(), l2.Line()); if (intersection.Intersecting()) { param1 = intersection.LineParameter1() / l1.Length(); param2 = intersection.LineParameter2() / l2.Length(); } else { param1 = param2 = std::numeric_limits::infinity(); } } bool Intersecting() const { return (T(0) <= param1 && param1 <= T(1)) && (T(0) <= param2 && param2 <= T(1)); } Vector Point() const { return lineSegment1.Interpol(param1); } T InterpolParameter1() const { return param1; } T InterpolParameter2() const { return param2; } T LineParameter1() const { return param1 * lineSegment1.Length(); } T LineParameter2() const { return param2 * lineSegment2.Length(); } private: T param1; T param2; LineSegment lineSegment1; LineSegment lineSegment2; }; // line segment vs line2d template class Intersection, Line> { public: Intersection(const LineSegment& line1, const Line& line2) { auto inter = Intersect(line1.Line(), line2); if (inter.Intersecting() && inter.LineParameter1() < line1.Length()) { param1 = inter.LineParameter1(); param2 = inter.LineParameter2(); } else { param1 = param2 = std::numeric_limits::infinity(); } } bool Intersecting() const { return !isinf(param1); } Vector Point() const { return line1.Line().PointAt(param1); } T LineParameter1() const { return param1; } T InterpolParameter1() const { return param1 / line1.Length(); } T LineParameter2() const { return param2; } private: T param1; T param2; LineSegment line1; }; template class Intersection, LineSegment> : private Intersection, Line> { public: Intersection(const Line& line1, const LineSegment& line2) : Intersection, Line>(line2, line1) {} using Intersection, Line>::Intersecting; using Intersection, Line>::Point; T LineParameter1() const { return Intersection, Line>::LineParameter2(); } T InterpolParameter2() const { return Intersection, Line>::InterpolParameter1(); } T LineParameter2() const { return Intersection, Line>::LineParameter1(); } }; // Ray-triangle intersection (M�ller-Trumbore algorithm) template class Intersection, Triangle3D> { using VectorT = Vector; public: Intersection(const Ray& ray, const Triangle3D& triangle); bool IsIntersecting() const { return intersecting; } VectorT Point() const { return point; } template U Interpolate(const U& a, const U& b, const U& c) const; T GetT() const { return t; } T GetU() const { return u; } T GetV() const { return v; } private: T t, u, v; bool intersecting; VectorT point; }; template Intersection, Triangle3D>::Intersection(const Ray& ray, const Triangle3D& triangle) { constexpr T EPSILON = T(0.00000001); VectorT edge1 = triangle.b - triangle.a; VectorT edge2 = triangle.c - triangle.a; VectorT h = Cross(ray.Direction(), edge2); T a = Dot(edge1, h); if (std::abs(a) < EPSILON) { intersecting = false; return; } T f = T(1) / a; VectorT s = ray.Base() - triangle.a; u = f * Dot(s, h); if (u < T(0) || u > T(1)) { intersecting = false; return; } VectorT q = Cross(s, edge1); v = f * Dot(ray.Direction(), q); if (v < 0.0 || u + v > 1.0) { intersecting = false; return; } t = f * Dot(edge2, q); intersecting = t > EPSILON; if (intersecting) { point = ray.PointAt(t); } } template template U Intersection, Triangle3D>::Interpolate(const U& a, const U& b, const U& c) const { T w = T(1) - u - v; return u * b + v * c + w * a; } template class BezierCurve { static_assert(Order >= 1, "Bezier curve must have order n>=1."); public: using VectorT = Vector; VectorT operator()(T t) const { return EvalInterpolRecurse(t); } protected: VectorT EvalInterpolRecurse(T t) const; public: std::array p; }; template auto BezierCurve::EvalInterpolRecurse(T t) const -> VectorT { std::array reduction = p; T u = T(1) - t; for (int i = Order; i >= 1; --i) { for (int j = 1; j <= i; ++j) { reduction[j - 1] = u * reduction[j - 1] + t * reduction[j]; } } return reduction[0]; } } // namespace mathter