#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2015 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_GEOMETRY_AREAS_TRIANGLE2_H #define NUCLEX_GEOMETRY_AREAS_TRIANGLE2_H #include "Nuclex/Geometry/Config.h" #include "Nuclex/Geometry/Point2.h" #include "Nuclex/Geometry/Vector2.h" namespace Nuclex { namespace Geometry { namespace Areas { // ------------------------------------------------------------------------------------------- // /// Triangle in 2D space constructed from its 3 corner vertices template struct Triangle2 { /// Initializes a triangle using the specified corner points /// First corner of the triangle /// Second corner of the triangle /// Third corner of the triangle public: Triangle2( const Point2 &a, const Point2 &b, const Point2 &c ) : A(a), B(b), C(c) {} /// Returns the center point of the triangle /// The center of the triangle /// /// This is also called the centroid - a cut-out triangle could be balanced on this /// center point, as it is the center of gravity. /// public: Point2 GetCenter() const { return Point2( (this->A.X + this->B.X + this->C.X) / 3, (this->A.Y + this->B.Y + this->C.Y) / 3 ); } /// Returns the circumcenter of the triangle /// The triangle's circumcenter /// /// The circumcenter is a point inside the triangle to which all three vertices /// have the same distance. Therefore, it also is the center of the smallest /// enclosing circle for triangles with a height of more than half their width. /// public: Point2 GetCircumcenter() const { TScalar magnitude = ( this->A.X * (this->C.Y - this->B.Y) + this->B.X * (this->A.Y - this->C.Y) + this->C.X * (this->B.Y - this->A.Y) ); Point2 circumcenter; if(magnitude == 0) { // Triangle is a flat degenerate, center within bounds std::pair extremeX = Math::Extremes( this->A.X, this->B.X, this->C.X ); circumcenter.X = (extremeX.first + extremeX.second) / 2; std::pair extremeY = Math::Extremes( this->A.Y, this->B.Y, this->C.Y ); circumcenter.Y = (extremeY.first + extremeY.second) / 2; } else { // Otherwise calculate the circumcenter TScalar a = (this->A.X * this->A.X) + (this->A.Y * this->A.Y); TScalar b = (this->B.X * this->B.X) + (this->B.Y * this->B.Y); TScalar c = (this->C.X * this->C.X) + (this->C.Y * this->C.Y); magnitude *= 2; circumcenter.X = ( a * (this->C.Y - this->B.Y) + b * (this->A.Y - this->C.Y) + c * (this->B.Y - this->A.Y) ) / magnitude; circumcenter.Y = -( a * (this->C.X - this->B.X) + b * (this->A.X - this->C.X) + c * (this->B.X - this->A.X) ) / magnitude; } return circumcenter; } /// Returns the incenter of the triangle /// The triangle's incenter /// /// The incenter is a point inside the triangle to which all three edges have /// the same distance. Therefore, it also serves as the center of the largest /// contained circle. /// public: Point2 GetIncenter() const { TScalar distanceAB = Point2::DistanceBetween(this->A, this->B); TScalar distanceBC = Point2::DistanceBetween(this->B, this->C); TScalar distanceCA = Point2::DistanceBetween(this->C, this->A); TScalar perimeter = distanceAB + distanceBC + distanceCA; if(perimeter == 0) { // Are all of the triangle's points are in the same place? return this->A; } else { // Triangle area > 0 TScalar x = (distanceBC * this->A.X + distanceCA * this->B.X + distanceAB * this->C.X); TScalar y = (distanceBC * this->A.Y + distanceCA * this->B.Y + distanceAB * this->C.Y); return Point2(x / perimeter, y / perimeter); } } /// Calculates the area of the triangle /// The area of the triangle /// /// Heron's triangle area formular states that, given s = (a + b + c) / 2 /// (semiperimeter), the area of a triangle can be calculated as /// /// /// _________________________________ /// area = \/ s * (s - a) * (s - b) * (s - c) /// /// /// /// In a paper by W. Kahan this method is proven to be numerically unstable /// for floating point numbers. He recommends to use the following formula /// instead, where the lengths a, b and c have to be sorted in ascending order. /// /// /// ______________________________________________________________ /// area = 0.25 * \/ (a + (b + c)) * (c - (a - b)) * (c + (a - b)) * (a + b - c)) /// /// /// public: TScalar GetArea() const { TScalar a = Point2::DistanceBetween(A, B); TScalar b = Point2::DistanceBetween(B, C); TScalar c = Point2::DistanceBetween(C, A); TScalar s = (a + b + c) / 2; return Math::SquareRoot(s * (s - a) * (s - b) * (s - c)); } /// Calculates the perimeter of the triangle /// The triangle's perimeter public: TScalar GetPerimeter() const { return ( Point2::DistanceBetween(A, B) + Point2::DistanceBetween(B, C) + Point2::DistanceBetween(C, A) ); } /// Offsets the entire triangle by the specified amount /// Amount the triangle will be offset on the X axis /// Amount the triangle will be offset on the Y axis public: void ShiftBy(TScalar amountX, TScalar amountY) { this->A.X += amountX; this->B.X += amountX; this->C.X += amountX; this->A.Y += amountY; this->B.Y += amountY; this->C.Y += amountY; } /// Offsets the entire triangle by the specified amount /// Amount the triangle will be offset public: void ShiftBy(const Vector2 &amount) { this->A += amount; this->B += amount; this->C += amount; } /// Checks whether the triangle is winding in clockwise order /// True if the triangle is winding in clockwise order public: bool IsWindingClockwise() const { float determinant = (this->B.X - this->A.X) * (this->C.Y - this->A.Y); determinant -= (this->B.Y - this->A.Y) * (this->C.X - this->A.X); return (determinant >= 0); } /// Checks whether the triangle is winding in counter-clockwise order /// True if the triangle is winding in counter-clockwise order public: bool IsWindingCounterClockwise() const { float determinant = (this->B.X - this->A.X) * (this->C.Y - this->A.Y); determinant -= (this->B.Y - this->A.Y) * (this->C.X - this->A.X); return (determinant < 0); } /// Fails an assertion if the triangle's points are not counter-clockwise public: void EnforceCounterClockwiseWinding() const { using namespace std; assert( IsWindingCounterClockwise() && "Triangle2 points have to be in counter-clockwise order" ); } /// Fails an assertion if the triangle's points are not clockwise public: void EnforceClockwiseWinding() const { using namespace std; assert( IsWindingClockwise() && "Triangle2 points have to be in clockwise order" ); } /// Coordinates of the triangle's first corner public: Point2 A; /// Coordinates of the triangle's second corner public: Point2 B; /// Coordinates of the triangle's third corner public: Point2 C; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Geometry::Areas #endif // NUCLEX_GEOMETRY_AREAS_TRIANGLE2_H