// -----------------------------------------------------------------------
//
// Original code by Frank Dockhorn, [not available anymore: http://sourceforge.net/projects/quadtreesim/]
// Triangle.NET code by Christian Woltering, http://triangle.codeplex.com/
//
// -----------------------------------------------------------------------
namespace UnityEngine.U2D.Animation.TriangleNet
.Tools
{
using System.Collections.Generic;
using System.Linq;
using Animation.TriangleNet.Geometry;
///
/// A Quadtree implementation optimized for triangles.
///
internal class TriangleQuadTree
{
QuadNode root;
internal ITriangle[] triangles;
internal int sizeBound;
internal int maxDepth;
///
/// Initializes a new instance of the class.
///
/// Mesh containing triangles.
/// The maximum depth of the tree.
/// The maximum number of triangles contained in a leaf.
///
/// The quadtree does not track changes of the mesh. If a mesh is refined or
/// changed in any other way, a new quadtree has to be built to make the point
/// location work.
///
/// A node of the tree will be split, if its level if less than the max depth parameter
/// AND the number of triangles in the node is greater than the size bound.
///
public TriangleQuadTree(Mesh mesh, int maxDepth = 10, int sizeBound = 10)
{
this.maxDepth = maxDepth;
this.sizeBound = sizeBound;
triangles = mesh.Triangles.ToArray();
int currentDepth = 0;
root = new QuadNode(mesh.Bounds, this, true);
root.CreateSubRegion(++currentDepth);
}
public ITriangle Query(double x, double y)
{
var point = new Point(x, y);
var indices = root.FindTriangles(point);
foreach (var i in indices)
{
var tri = this.triangles[i];
if (IsPointInTriangle(point, tri.GetVertex(0), tri.GetVertex(1), tri.GetVertex(2)))
{
return tri;
}
}
return null;
}
///
/// Test, if a given point lies inside a triangle.
///
/// Point to locate.
/// Corner point of triangle.
/// Corner point of triangle.
/// Corner point of triangle.
/// True, if point is inside or on the edge of this triangle.
internal static bool IsPointInTriangle(Point p, Point t0, Point t1, Point t2)
{
// TODO: no need to create new Point instances here
Point d0 = new Point(t1.x - t0.x, t1.y - t0.y);
Point d1 = new Point(t2.x - t0.x, t2.y - t0.y);
Point d2 = new Point(p.x - t0.x, p.y - t0.y);
// crossproduct of (0, 0, 1) and d0
Point c0 = new Point(-d0.y, d0.x);
// crossproduct of (0, 0, 1) and d1
Point c1 = new Point(-d1.y, d1.x);
// Linear combination d2 = s * d0 + v * d1.
//
// Multiply both sides of the equation with c0 and c1
// and solve for s and v respectively
//
// s = d2 * c1 / d0 * c1
// v = d2 * c0 / d1 * c0
double s = DotProduct(d2, c1) / DotProduct(d0, c1);
double v = DotProduct(d2, c0) / DotProduct(d1, c0);
if (s >= 0 && v >= 0 && ((s + v) <= 1))
{
// Point is inside or on the edge of this triangle.
return true;
}
return false;
}
internal static double DotProduct(Point p, Point q)
{
return p.x * q.x + p.y * q.y;
}
///
/// A node of the quadtree.
///
class QuadNode
{
const int SW = 0;
const int SE = 1;
const int NW = 2;
const int NE = 3;
const double EPS = 1e-6;
static readonly byte[] BITVECTOR = { 0x1, 0x2, 0x4, 0x8 };
Rectangle bounds;
Point pivot;
TriangleQuadTree tree;
QuadNode[] regions;
List triangles;
byte bitRegions;
public QuadNode(Rectangle box, TriangleQuadTree tree)
: this(box, tree, false)
{
}
public QuadNode(Rectangle box, TriangleQuadTree tree, bool init)
{
this.tree = tree;
this.bounds = new Rectangle(box.Left, box.Bottom, box.Width, box.Height);
this.pivot = new Point((box.Left + box.Right) / 2, (box.Bottom + box.Top) / 2);
this.bitRegions = 0;
this.regions = new QuadNode[4];
this.triangles = new List();
if (init)
{
int count = tree.triangles.Length;
// Allocate memory upfront
triangles.Capacity = count;
for (int i = 0; i < count; i++)
{
triangles.Add(i);
}
}
}
public List FindTriangles(Point searchPoint)
{
int region = FindRegion(searchPoint);
if (regions[region] == null)
{
return triangles;
}
return regions[region].FindTriangles(searchPoint);
}
public void CreateSubRegion(int currentDepth)
{
// The four sub regions of the quad tree
// +--------------+
// | nw 2 | ne 3 |
// |------+pivot--|
// | sw 0 | se 1 |
// +--------------+
Rectangle box;
var width = bounds.Right - pivot.x;
var height = bounds.Top - pivot.y;
// 1. region south west
box = new Rectangle(bounds.Left, bounds.Bottom, width, height);
regions[0] = new QuadNode(box, tree);
// 2. region south east
box = new Rectangle(pivot.x, bounds.Bottom, width, height);
regions[1] = new QuadNode(box, tree);
// 3. region north west
box = new Rectangle(bounds.Left, pivot.y, width, height);
regions[2] = new QuadNode(box, tree);
// 4. region north east
box = new Rectangle(pivot.x, pivot.y, width, height);
regions[3] = new QuadNode(box, tree);
Point[] triangle = new Point[3];
// Find region for every triangle vertex
foreach (var index in triangles)
{
ITriangle tri = tree.triangles[index];
triangle[0] = tri.GetVertex(0);
triangle[1] = tri.GetVertex(1);
triangle[2] = tri.GetVertex(2);
AddTriangleToRegion(triangle, index);
}
for (int i = 0; i < 4; i++)
{
if (regions[i].triangles.Count > tree.sizeBound && currentDepth < tree.maxDepth)
{
regions[i].CreateSubRegion(currentDepth + 1);
}
}
}
void AddTriangleToRegion(Point[] triangle, int index)
{
bitRegions = 0;
if (TriangleQuadTree.IsPointInTriangle(pivot, triangle[0], triangle[1], triangle[2]))
{
AddToRegion(index, SW);
AddToRegion(index, SE);
AddToRegion(index, NW);
AddToRegion(index, NE);
return;
}
FindTriangleIntersections(triangle, index);
if (bitRegions == 0)
{
// we didn't find any intersection so we add this triangle to a point's region
int region = FindRegion(triangle[0]);
regions[region].triangles.Add(index);
}
}
void FindTriangleIntersections(Point[] triangle, int index)
{
// PLEASE NOTE:
// Handling of component comparison is tightly associated with the implementation
// of the findRegion() function. That means when the point to be compared equals
// the pivot point the triangle must be put at least into region 2.
//
// Linear equations are in parametric form.
// pivot.x = triangle[0].x + t * (triangle[1].x - triangle[0].x)
// pivot.y = triangle[0].y + t * (triangle[1].y - triangle[0].y)
int k = 2;
double dx, dy;
// Iterate through all triangle laterals and find bounding box intersections
for (int i = 0; i < 3; k = i++)
{
dx = triangle[i].x - triangle[k].x;
dy = triangle[i].y - triangle[k].y;
if (dx != 0.0)
{
FindIntersectionsWithX(dx, dy, triangle, index, k);
}
if (dy != 0.0)
{
FindIntersectionsWithY(dx, dy, triangle, index, k);
}
}
}
void FindIntersectionsWithX(double dx, double dy, Point[] triangle, int index, int k)
{
double t;
// find intersection with plane x = m_pivot.dX
t = (pivot.x - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NW);
AddToRegion(index, NE);
}
}
// find intersection with plane x = m_boundingBox[0].dX
t = (bounds.Left - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SW);
}
else if (yComponent <= bounds.Top) // TODO: check && yComponent >= pivot.Y
{
AddToRegion(index, NW);
}
}
// find intersection with plane x = m_boundingBox[1].dX
t = (bounds.Right - triangle[k].x) / dx;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
double yComponent = triangle[k].y + t * dy;
if (yComponent < pivot.y && yComponent >= bounds.Bottom)
{
AddToRegion(index, SE);
}
else if (yComponent <= bounds.Top)
{
AddToRegion(index, NE);
}
}
}
void FindIntersectionsWithY(double dx, double dy, Point[] triangle, int index, int k)
{
double t, xComponent;
// find intersection with plane y = m_pivot.dY
t = (pivot.y - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
AddToRegion(index, NW);
}
}
// find intersection with plane y = m_boundingBox[0].dY
t = (bounds.Bottom - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, SE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, SW);
}
}
// find intersection with plane y = m_boundingBox[1].dY
t = (bounds.Top - triangle[k].y) / dy;
if (t < (1 + EPS) && t > -EPS)
{
// we have an intersection
xComponent = triangle[k].x + t * dx;
if (xComponent > pivot.x && xComponent <= bounds.Right)
{
AddToRegion(index, NE);
}
else if (xComponent >= bounds.Left)
{
AddToRegion(index, NW);
}
}
}
int FindRegion(Point point)
{
int b = 2;
if (point.y < pivot.y)
{
b = 0;
}
if (point.x > pivot.x)
{
b++;
}
return b;
}
void AddToRegion(int index, int region)
{
//if (!(m_bitRegions & BITVECTOR[region]))
if ((bitRegions & BITVECTOR[region]) == 0)
{
regions[region].triangles.Add(index);
bitRegions |= BITVECTOR[region];
}
}
}
}
}