#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_GENERATORS_TRIANGLE2GENERATOR_H
#define NUCLEX_GEOMETRY_AREAS_GENERATORS_TRIANGLE2GENERATOR_H
#include "Nuclex/Geometry/Config.h"
#include "Nuclex/Geometry/Areas/Triangle2.h"
#include "Nuclex/Geometry/Areas/Rectangle2.h"
#include "Nuclex/Geometry/Areas/Disc2.h"
namespace Nuclex { namespace Geometry { namespace Areas { namespace Generators {
// ------------------------------------------------------------------------------------------- //
/// Point and area generation methods for 2D triangles
template
class Triangle2Generator {
/// Returns a random point within the triangle
/// Triangle in which a random point will be generated
/// Random number generator that will be used
/// A random point inside the triangle
public: template
static Point2 GetRandomPointInside(
const Triangle2 &triangle, TRandomNumberEngine &random
) {
// Treat triangle as one half of a parallelogram. Then calculate a
// random point along the width and the height.
//
// Width
// -----------
// a___________
// / / /
// / / / Height
// /__________/ /
// b c
//
// Now split the parallelogram along a-c and mirror any points that
// end up on the other side.
//
// xx = rand(1.0)
// yy = rand(1.0)
//
// if((xx + yy) > 1.0) {
// xx = 1.0 - xx;
// yy = 1.0 - yy;
// }
//
// Then calculate the absolute coordinates of the point
//
// a.
// / .
// / .
// /________.
// b c
//
// x = b.x + xx * (c.x - b.x) + (b.x - a.x) * yy
// y = yy * (a.y - b.y)
// This might be using the same approach, not sure though :)
// http://vcg.isti.cnr.it/activities/geometryegraphics/pointintetraedro.html
// Calculate random barycentric coordinates inside the unit
// triangle (0,0)-(1,0)-(0,1). First, we take random x and y coordinates in
// a box with side lengths ab, ac
TScalar x = Math::Random(random, 1);
TScalar y = Math::Random(random, 1);
// The triangle covers exactly half of the box our random points are distributed
// in. Instead of rejecting these coordinates, we mirror the other half of the box
// back into the triangle (on the bc edge of the triangle)
if(x + y > 1) {
x = 1 - x;
y = 1 - y;
}
// Convert the barycentric coordinates back into cartesian coordinates
TScalar z = 1 - x - y;
return (triangle.A * x) + (triangle.B * y) + (triangle.C * z);
}
/// Returns a random point on the outline of the triangle
///
/// Triangle on whose outline a random point will be generated
///
/// Random number generator that will be used
/// A random point on the triangle's outline
public: template
static Point2 GetRandomPointOnOutline(
const Triangle2 &triangle, TRandomNumberEngine &random
) {
Vector2 ab = (triangle.B - triangle.A);
Vector2 bc = (triangle.C - triangle.B);
Vector2 ca = (triangle.A - triangle.C);
float lengthAB = ab.GetSquaredLength();
float lengthBC = bc.GetSquaredLength();
float lengthCA = ca.GetSquaredLength();
float totalLength = lengthAB + lengthBC + lengthCA;
float position = Math::Random(random, totalLength);
// Is the position on the A->B edge?
if(position < lengthAB) {
float t = (position / lengthAB);
return triangle.A + (ab * t);
}
// Is the position on the B->C edge?
float lengthAC = lengthAB + lengthBC;
if(position < lengthAC) {
float t = ((position - lengthAB) / lengthBC);
return triangle.B + (bc * t);
}
// Position is on the C->A edge!
{
float t = ((position - lengthAC) / lengthCA);
return triangle.C + (ca * t);
}
}
/// Determines the closest point to another point inside the triangle
/// Triangle in which the closest point will be found
/// Point to which the closest point will be determined
/// The closest point to the specified point inside the triangle
public: static Point2 GetClosestPointInside(
const Triangle2 &triangle, const Point2 &point
) {
Vector2 offsetAB = triangle.B - triangle.A;
Vector2 offsetAPoint = point - triangle.A;
TScalar crossAPoint = (offsetAB.X * offsetAPoint.Y) - (offsetAB.Y * offsetAPoint.X);
Vector2 offsetBC = triangle.C - triangle.B;
Vector2 offsetBPoint = point - triangle.B;
TScalar crossBPoint = (offsetBC.X * offsetBPoint.Y) - (offsetBC.Y * offsetBPoint.X);
Vector2 offsetCA = triangle.A - triangle.C;
Vector2 offsetCPoint = point - triangle.C;
TScalar crossCPoint = (offsetCA.X * offsetCPoint.Y) - (offsetCA.Y * offsetCPoint.X);
// A
// O
// / \
// AB / \ CA
// / \
// O-------O
// B BC C
if(crossCPoint < 0) { // Point lies outside on the CA edge
// Note: the point can't be outside all three edges, so we ignore this possibility!
if(crossAPoint < 0) { // Point lies outside the AB and CA edges -> vertex A
return triangle.A;
} else if(crossBPoint < 0) { // Point lies outside the BC and CA edges -> vertex C
return triangle.C;
} else { // Point lies inside the AB and BC edges -> along edge CA
TScalar wd = Math::Clamp(
((offsetCA.X * offsetCPoint.X) + (offsetCA.Y * offsetCPoint.Y)) /
((offsetCA.X * offsetCA.X) + (offsetCA.Y * offsetCA.Y)),
0, 1
);
return Point2(
triangle.C.X + (triangle.A.X - triangle.C.X) * wd,
triangle.C.Y + (triangle.A.Y - triangle.C.Y) * wd
);
}
} else { // Point lies inside the CA edge
if(crossAPoint < 0) {
if(crossBPoint < 0) { // Point lies outside the AB and BC edges -> vertex B
return triangle.B;
} else { // Point lies inside the BC and CA edges -> along edge AB
TScalar wd = Math::Clamp(
((offsetAB.X * offsetAPoint.X) + (offsetAB.Y * offsetAPoint.Y)) /
((offsetAB.X * offsetAB.X) + (offsetAB.Y * offsetAB.Y)),
0, 1
);
return Point2(
triangle.A.X + (triangle.B.X - triangle.A.X) * wd,
triangle.A.Y + (triangle.B.Y - triangle.A.Y) * wd
);
}
} else {
if(crossBPoint < 0) { // Point lies inside the AB and CA edges -> along edge BC
TScalar wd = Math::Clamp(
((offsetBC.X * offsetBPoint.X) + (offsetBC.Y * offsetBPoint.Y)) /
((offsetBC.X * offsetBC.X) + (offsetBC.Y * offsetBC.Y)),
0, 1
);
return Point2(
triangle.B.X + (triangle.C.X - triangle.B.X) * wd,
triangle.B.Y + (triangle.C.Y - triangle.B.Y) * wd
);
} else { // Point lies inside all edges -> contained
// If the point is parallel to one edge and yet inside the triangle,
// the triangle is degenerate and the point hit it
if(crossAPoint == 0) {
std::pair rangeX = Math::Extremes(
triangle.A.X, triangle.B.X, triangle.C.X
);
std::pair rangeY = Math::Extremes(
triangle.A.Y, triangle.B.Y, triangle.C.Y
);
return Point2(
Math::Clamp(point.X, rangeX.first, rangeX.second),
Math::Clamp(point.Y, rangeY.first, rangeY.second)
);
} else { // Point is inside a non-degenerate triangle
return point;
}
}
}
}
}
/// Determines the closest point to another point on the triangle's outline
/// Triangle on whose outline the closest point will be found
/// Point to which the closest point will be determined
/// The closest point to the specified point on the triangle's outline
public: static Point2 GetClosestPointOnOutline(
const Triangle2 &triangle, const Point2 &point
) {
Point2 closestAB = getClosestPointOnSegment(
triangle.A, triangle.B, point - triangle.A
);
Point2 closestBC = getClosestPointOnSegment(
triangle.B, triangle.C, point - triangle.B
);
Point2 closestCA = getClosestPointOnSegment(
triangle.C, triangle.A, point - triangle.C
);
TScalar squaredDistanceAB = point.SquaredDistanceTo(closestAB);
TScalar squaredDistanceBC = point.SquaredDistanceTo(closestBC);
TScalar squaredDistanceCA = point.SquaredDistanceTo(closestCA);
if(squaredDistanceAB < squaredDistanceBC) {
if(squaredDistanceAB < squaredDistanceCA) {
return closestAB;
} else {
return closestCA;
}
} else {
if(squaredDistanceBC < squaredDistanceCA) {
return closestBC;
} else {
return closestCA;
}
}
}
/// Calculates the bounding rectangle of a triangle
/// Triangle of which the bounding rectangle will be calculated
/// The triangle's bounding rectangle
public: static Rectangle2 GetBoundingRectangle(
const Triangle2 &triangle
) {
Rectangle2 boundingRectangle;
{
std::pair extremesX = Math::Extremes(
triangle.A.X, triangle.B.X, triangle.C.X
);
boundingRectangle.Min.X = extremesX.first;
boundingRectangle.Max.X = extremesX.second;
}
{
std::pair extremesY = Math::Extremes(
triangle.A.Y, triangle.B.Y, triangle.C.Y
);
boundingRectangle.Min.Y = extremesY.first;
boundingRectangle.Max.Y = extremesY.second;
}
return boundingRectangle;
}
/// Calculates the bounding disc of a triangle
/// Triangle of which the bounding disc will be calculated
/// The triangle's bounding disc
///
/// Based on "Minimum bounding circle for a triangle" article by
/// Christer Ericson published at http://realtimecollisiondetection.net/blog/?p=20
///
public: static Disc2 GetBoundingDisc(const Triangle2 &triangle) {
Vector2 ab = triangle.B - triangle.A;
Vector2 ac = triangle.C - triangle.A;
TScalar dotABAB = Vector2::Dot(ab, ab);
TScalar dotABAC = Vector2::Dot(ab, ac);
TScalar dotACAC = Vector2::Dot(ac, ac);
Disc2 boundingDisc;
TScalar d = 2 * (dotABAB * dotACAC - dotABAC * dotABAC);
if(d == 0) { // Triangle is a flat degenerate, center within bounds
std::pair extremeX = Math::Extremes(
triangle.A.X, triangle.B.X, triangle.C.X
);
boundingDisc.Center.X = (extremeX.first + extremeX.second) / 2;
std::pair extremeY = Math::Extremes(
triangle.A.Y, triangle.B.Y, triangle.C.Y
);
boundingDisc.Center.Y = (extremeY.first + extremeY.second) / 2;
boundingDisc.Radius = boundingDisc.Center.DistanceTo(
extremeX.first, extremeY.first
);
} else { // Otherwise use either the circumcenter or the longest edge
TScalar s = (dotABAB * dotACAC - dotACAC * dotABAC) / d;
TScalar t = (dotACAC * dotABAB - dotABAB * dotABAC) / d;
if(s <= 0) {
boundingDisc.Center.X = (triangle.A.X + triangle.C.X) / 2;
boundingDisc.Center.Y = (triangle.A.Y + triangle.C.Y) / 2;
boundingDisc.Radius = boundingDisc.Center.DistanceTo(triangle.A);
} else if(t <= 0) {
boundingDisc.Center.X = (triangle.A.X + triangle.B.X) / 2;
boundingDisc.Center.Y = (triangle.A.Y + triangle.B.Y) / 2;
boundingDisc.Radius = boundingDisc.Center.DistanceTo(triangle.A);
} else if(s + t >= 1) {
boundingDisc.Center.X = (triangle.B.X + triangle.C.X) / 2;
boundingDisc.Center.Y = (triangle.B.Y + triangle.C.Y) / 2;
boundingDisc.Radius = boundingDisc.Center.DistanceTo(triangle.B);
} else {
boundingDisc.Center = triangle.A + s * ab + t * ac;
boundingDisc.Radius = boundingDisc.Center.DistanceTo(triangle.A);
}
}
return boundingDisc;
}
// Could add a GetEnclosedDisc() method here...
/// Finds the closest point to an offset on a line segment
/// Point at which the line segment begins
/// Point at which the line segment ends
/// Offset of the line segment from the line's start
/// The closest point on the line segment to the offset
private: static Point2 getClosestPointOnSegment(
const Point2 &start, const Point2 &end,
const Vector2 &offset
) {
Vector2 direction = (end - start);
TScalar t = Vector2::Dot(offset, direction);
if(t < 0) {
return start;
} else {
TScalar squaredSegmentLength = direction.GetSquaredLength();
if(t > squaredSegmentLength) {
return end;
} else {
return start + (direction * (t / squaredSegmentLength));
}
}
}
};
// ------------------------------------------------------------------------------------------- //
}}}} // namespace Nuclex::Geometry::Areas::Generators
#endif // NUCLEX_GEOMETRY_AREAS_GENERATORS_TRIANGLE2GENERATOR_H