// ----------------------------------------------------------------------- // // 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]; } } } } }