#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