// Copyright (c) 2014-2015 Robert Rouhani and other contributors (see CONTRIBUTORS file). // Licensed under the MIT License - https://raw.github.com/Robmaister/SharpNav/master/LICENSE using System; using System.Collections.Generic; #if MONOGAME using Vector3 = Microsoft.Xna.Framework.Vector3; #elif OPENTK using Vector3 = OpenTK.Vector3; #elif SHARPDX using Vector3 = SharpDX.Vector3; #endif namespace SharpNav.Collections { /// /// A tree of bounding volumes. /// public class BVTree { private static readonly Node.CompareX XComparer = new Node.CompareX(); private static readonly Node.CompareY YComparer = new Node.CompareY(); private static readonly Node.CompareZ ZComparer = new Node.CompareZ(); /// /// Nodes in the tree /// private Node[] nodes; /// /// Initializes a new instance of the class. /// /// A set of vertices. /// A set of polygons composed of the vertices in verts. /// The maximum number of vertices per polygon. /// The size of a cell. /// The height of a cell. public BVTree(PolyVertex[] verts, PolyMesh.Polygon[] polys, int nvp, float cellSize, float cellHeight) { nodes = new Node[polys.Length * 2]; var items = new List(); for (int i = 0; i < polys.Length; i++) { PolyMesh.Polygon p = polys[i]; Node temp; temp.Index = i; temp.Bounds.Min = temp.Bounds.Max = verts[p.Vertices[0]]; for (int j = 1; j < nvp; j++) { int vi = p.Vertices[j]; if (vi == PolyMesh.NullId) break; var v = verts[vi]; PolyVertex.ComponentMin(ref temp.Bounds.Min, ref v, out temp.Bounds.Min); PolyVertex.ComponentMax(ref temp.Bounds.Max, ref v, out temp.Bounds.Max); } temp.Bounds.Min.Y = (int)Math.Floor((float)temp.Bounds.Min.Y * cellHeight / cellSize); temp.Bounds.Max.Y = (int)Math.Ceiling((float)temp.Bounds.Max.Y * cellHeight / cellSize); items.Add(temp); } Subdivide(items, 0, items.Count, 0); } /// /// Gets the number of nodes in the tree. /// public int Count { get { return nodes.Length; } } /// /// Gets the node at a specified index. /// /// The index. /// The node at the index. public Node this[int index] { get { return nodes[index]; } } /// /// Calculates the bounding box for a set of bounding boxes. /// /// The list of all the bounding boxes. /// The first bounding box in the list to get the extends of. /// The last bounding box in the list to get the extends of. /// The extends of all the bounding boxes. private static void CalcExtends(List items, int minIndex, int maxIndex, out PolyBounds bounds) { bounds = items[minIndex].Bounds; for (int i = minIndex + 1; i < maxIndex; i++) { Node it = items[i]; PolyVertex.ComponentMin(ref it.Bounds.Min, ref bounds.Min, out bounds.Min); PolyVertex.ComponentMax(ref it.Bounds.Max, ref bounds.Max, out bounds.Max); } } /// /// Determine whether the bounding x, y, or z axis contains the longest distance /// /// Length of bounding x-axis /// Length of bounding y-axis /// Length of bounding z-axis /// Returns the a specific axis (x, y, or z) private static int LongestAxis(int x, int y, int z) { int axis = 0; int max = x; if (y > max) { axis = 1; max = y; } if (z > max) axis = 2; return axis; } /// /// Subdivides a list of bounding boxes until it is a tree. /// /// A list of bounding boxes. /// The first index to consider (recursively). /// The last index to consier (recursively). /// The current node to look at. /// The current node at the end of each method. private int Subdivide(List items, int minIndex, int maxIndex, int curNode) { int numIndex = maxIndex - minIndex; int curIndex = curNode; int oldNode = curNode; curNode++; //Check if the current node is a leaf node if (numIndex == 1) nodes[oldNode] = items[minIndex]; else { PolyBounds bounds; CalcExtends(items, minIndex, maxIndex, out bounds); nodes[oldNode].Bounds = bounds; int axis = LongestAxis((int)(bounds.Max.X - bounds.Min.X), (int)(bounds.Max.Y - bounds.Min.Y), (int)(bounds.Max.Z - bounds.Min.Z)); switch (axis) { case 0: items.Sort(minIndex, numIndex, XComparer); break; case 1: items.Sort(minIndex, numIndex, YComparer); break; case 2: items.Sort(minIndex, numIndex, ZComparer); break; default: break; } int splitIndex = minIndex + (numIndex / 2); curNode = Subdivide(items, minIndex, splitIndex, curNode); curNode = Subdivide(items, splitIndex, maxIndex, curNode); int escapeIndex = curNode - curIndex; nodes[oldNode].Index = -escapeIndex; } return curNode; } /// /// The data stored in a bounding volume node. /// public struct Node { /// /// The bounding box of the node. /// public PolyBounds Bounds; /// /// The index of this node in a . /// public int Index; /// /// An implementation that only compares two s on the X axis. /// public class CompareX : IComparer { /// /// Compares two nodes's bounds on the X axis. /// /// A node. /// Another node. /// A negative value if a is less than b; 0 if they are equal; a positive value of a is greater than b. public int Compare(Node x, Node y) { if (x.Bounds.Min.X < y.Bounds.Min.X) return -1; if (x.Bounds.Min.X > y.Bounds.Min.X) return 1; return 0; } } /// /// An implementation that only compares two s on the Y axis. /// public class CompareY : IComparer { /// /// Compares two nodes's bounds on the Y axis. /// /// A node. /// Another node. /// A negative value if a is less than b; 0 if they are equal; a positive value of a is greater than b. public int Compare(Node x, Node y) { if (x.Bounds.Min.Y < y.Bounds.Min.Y) return -1; if (x.Bounds.Min.Y > y.Bounds.Min.Y) return 1; return 0; } } /// /// An implementation that only compares two s on the Z axis. /// public class CompareZ : IComparer { /// /// Compares two nodes's bounds on the Z axis. /// /// A node. /// Another node. /// A negative value if a is less than b; 0 if they are equal; a positive value of a is greater than b. public int Compare(Node x, Node y) { if (x.Bounds.Min.Z < y.Bounds.Min.Z) return -1; if (x.Bounds.Min.Z > y.Bounds.Min.Z) return 1; return 0; } } } } }