Advertisement
Guest User

Untitled

a guest
Feb 18th, 2018
114
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 11.46 KB | None | 0 0
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3.  
  4. // A Dynamic, Loose Octree for storing any objects that can be described with AABB bounds
  5. // See also: PointOctree, where objects are stored as single points and some code can be simplified
  6. // Octree:  An octree is a tree data structure which divides 3D space into smaller partitions (nodes)
  7. //          and places objects into the appropriate nodes. This allows fast access to objects
  8. //          in an area of interest without having to check every object.
  9. // Dynamic: The octree grows or shrinks as required when objects as added or removed
  10. //          It also splits and merges nodes as appropriate. There is no maximum depth.
  11. //          Nodes have a constant - numObjectsAllowed - which sets the amount of items allowed in a node before it splits.
  12. // Loose:   The octree's nodes can be larger than 1/2 their parent's length and width, so they overlap to some extent.
  13. //          This can alleviate the problem of even tiny objects ending up in large nodes if they're near boundaries.
  14. //          A looseness value of 1.0 will make it a "normal" octree.
  15. // T:       The content of the octree can be anything, since the bounds data is supplied separately.
  16.  
  17. // Originally written for my game Scraps (http://www.scrapsgame.com) but intended to be general-purpose.
  18. // Copyright 2014 Nition, BSD licence (see LICENCE file). http://nition.co
  19. // Unity-based, but could be adapted to work in pure C#
  20.  
  21. // Note: For loops are often used here since in some cases (e.g. the IsColliding method)
  22. // they actually give much better performance than using Foreach, even in the compiled build.
  23. // Using a LINQ expression is worse again than Foreach.
  24. public class BoundsOctree<T> {
  25.     // The total amount of objects currently in the tree
  26.     public int Count { get; private set; }
  27.    
  28.     // Root node of the octree
  29.     BoundsOctreeNode<T> rootNode;
  30.     // Should be a value between 1 and 2. A multiplier for the base size of a node.
  31.     // 1.0 is a "normal" octree, while values > 1 have overlap
  32.     readonly float looseness;
  33.     // Size that the octree was on creation
  34.     readonly float initialSize;
  35.     // Minimum side length that a node can be - essentially an alternative to having a max depth
  36.     readonly float minSize;
  37.     // For collision visualisation. Automatically removed in builds.
  38.     #if UNITY_EDITOR
  39.     const int numCollisionsToSave = 4;
  40.     readonly Queue<Bounds> lastBoundsCollisionChecks = new Queue<Bounds>();
  41.     readonly Queue<Ray> lastRayCollisionChecks = new Queue<Ray>();
  42.     #endif
  43.  
  44.     /// <summary>
  45.     /// Constructor for the bounds octree.
  46.     /// </summary>
  47.     /// <param name="initialWorldSize">Size of the sides of the initial node, in metres. The octree will never shrink smaller than this.</param>
  48.     /// <param name="initialWorldPos">Position of the centre of the initial node.</param>
  49.     /// <param name="minNodeSize">Nodes will stop splitting if the new nodes would be smaller than this (metres).</param>
  50.     /// <param name="loosenessVal">Clamped between 1 and 2. Values > 1 let nodes overlap.</param>
  51.     public BoundsOctree(float initialWorldSize, Vector3 initialWorldPos, float minNodeSize, float loosenessVal) {
  52.         if (minNodeSize > initialWorldSize) {
  53.             Debug.LogWarning("Minimum node size must be at least as big as the initial world size. Was: " + minNodeSize + " Adjusted to: " + initialWorldSize);
  54.             minNodeSize = initialWorldSize;
  55.         }
  56.         Count = 0;
  57.         initialSize = initialWorldSize;
  58.         minSize = minNodeSize;
  59.         looseness = Mathf.Clamp(loosenessVal, 1.0f, 2.0f);
  60.         rootNode = new BoundsOctreeNode<T>(initialSize, minSize, loosenessVal, initialWorldPos);
  61.     }
  62.  
  63.     // #### PUBLIC METHODS ####
  64.  
  65.     /// <summary>
  66.     /// Add an object.
  67.     /// </summary>
  68.     /// <param name="obj">Object to add.</param>
  69.     /// <param name="objBounds">3D bounding box around the object.</param>
  70.     public void Add(T obj, Bounds objBounds) {
  71.         // Add object or expand the octree until it can be added
  72.         int count = 0; // Safety check against infinite/excessive growth
  73.         while (!rootNode.Add(obj, objBounds)) {
  74.             Grow(objBounds.center - rootNode.Center);
  75.             if (++count > 20) {
  76.                 Debug.LogError("Aborted Add operation as it seemed to be going on forever (" + (count - 1) + ") attempts at growing the octree.");
  77.                 return;
  78.             }
  79.         }
  80.         Count++;
  81.     }
  82.  
  83.     /// <summary>
  84.     /// Remove an object. Makes the assumption that the object only exists once in the tree.
  85.     /// </summary>
  86.     /// <param name="obj">Object to remove.</param>
  87.     /// <returns>True if the object was removed successfully.</returns>
  88.     public bool Remove(T obj) {
  89.         bool removed = rootNode.Remove(obj);
  90.  
  91.         // See if we can shrink the octree down now that we've removed the item
  92.         if (removed) {
  93.             Count--;
  94.             Shrink();
  95.         }
  96.  
  97.         return removed;
  98.     }
  99.  
  100.     /// <summary>
  101.     /// Check if the specified bounds intersect with anything in the tree. See also: GetColliding.
  102.     /// </summary>
  103.     /// <param name="checkBounds">bounds to check.</param>
  104.     /// <returns>True if there was a collision.</returns>
  105.     public bool IsColliding(Bounds checkBounds) {
  106.         //#if UNITY_EDITOR
  107.         // For debugging
  108.         //AddCollisionCheck(checkBounds);
  109.         //#endif
  110.         return rootNode.IsColliding(ref checkBounds);
  111.     }
  112.  
  113.     /// <summary>
  114.     /// Check if the specified ray intersects with anything in the tree. See also: GetColliding.
  115.     /// </summary>
  116.     /// <param name="checkRay">ray to check.</param>
  117.     /// <param name="maxDistance">distance to check.</param>
  118.     /// <returns>True if there was a collision.</returns>
  119.     public bool IsColliding(Ray checkRay, float maxDistance) {
  120.         //#if UNITY_EDITOR
  121.         // For debugging
  122.         //AddCollisionCheck(checkRay);
  123.         //#endif
  124.         return rootNode.IsColliding(ref checkRay, maxDistance);
  125.     }
  126.  
  127.     /// <summary>
  128.     /// Returns an array of objects that intersect with the specified bounds, if any. Otherwise returns an empty array. See also: IsColliding.
  129.     /// </summary>
  130.     /// <param name="collidingWith">list to store intersections.</param>
  131.     /// <param name="checkBounds">bounds to check.</param>
  132.     /// <returns>Objects that intersect with the specified bounds.</returns>
  133.     public void GetColliding(List<T> collidingWith, Bounds checkBounds) {
  134.         //#if UNITY_EDITOR
  135.         // For debugging
  136.         //AddCollisionCheck(checkBounds);
  137.         //#endif
  138.         rootNode.GetColliding(ref checkBounds, collidingWith);
  139.     }
  140.  
  141.     /// <summary>
  142.     /// Returns an array of objects that intersect with the specified ray, if any. Otherwise returns an empty array. See also: IsColliding.
  143.     /// </summary>
  144.     /// <param name="collidingWith">list to store intersections.</param>
  145.     /// <param name="checkRay">ray to check.</param>
  146.     /// <param name="maxDistance">distance to check.</param>
  147.     /// <returns>Objects that intersect with the specified ray.</returns>
  148.     public void GetColliding(List<T> collidingWith, Ray checkRay, float maxDistance = float.PositiveInfinity) {
  149.         //#if UNITY_EDITOR
  150.         // For debugging
  151.         //AddCollisionCheck(checkRay);
  152.         //#endif
  153.         rootNode.GetColliding(ref checkRay, collidingWith, maxDistance);
  154.     }
  155.  
  156.     public Bounds GetMaxBounds()
  157.     {
  158.         return rootNode.GetBounds();
  159.     }
  160.  
  161.     /// <summary>
  162.     /// Draws node boundaries visually for debugging.
  163.     /// Must be called from OnDrawGizmos externally. See also: DrawAllObjects.
  164.     /// </summary>
  165.     public void DrawAllBounds() {
  166.         rootNode.DrawAllBounds();
  167.     }
  168.  
  169.     /// <summary>
  170.     /// Draws the bounds of all objects in the tree visually for debugging.
  171.     /// Must be called from OnDrawGizmos externally. See also: DrawAllBounds.
  172.     /// </summary>
  173.     public void DrawAllObjects() {
  174.         rootNode.DrawAllObjects();
  175.     }
  176.  
  177.     // Intended for debugging. Must be called from OnDrawGizmos externally
  178.     // See also DrawAllBounds and DrawAllObjects
  179.     /// <summary>
  180.     /// Visualises collision checks from IsColliding and GetColliding.
  181.     /// Collision visualisation code is automatically removed from builds so that collision checks aren't slowed down.
  182.     /// </summary>
  183.     #if UNITY_EDITOR
  184.     public void DrawCollisionChecks() {
  185.         int count = 0;
  186.         foreach (Bounds collisionCheck in lastBoundsCollisionChecks) {
  187.             Gizmos.color = new Color(1.0f, 1.0f - ((float)count / numCollisionsToSave), 1.0f);
  188.             Gizmos.DrawCube(collisionCheck.center, collisionCheck.size);
  189.             count++;
  190.         }
  191.  
  192.         foreach (Ray collisionCheck in lastRayCollisionChecks) {
  193.             Gizmos.color = new Color(1.0f, 1.0f - ((float)count / numCollisionsToSave), 1.0f);
  194.             Gizmos.DrawRay(collisionCheck.origin, collisionCheck.direction);
  195.             count++;
  196.         }
  197.         Gizmos.color = Color.white;
  198.     }
  199.     #endif
  200.  
  201.     // #### PRIVATE METHODS ####
  202.  
  203.     /// <summary>
  204.     /// Used for visualising collision checks with DrawCollisionChecks.
  205.     /// Automatically removed from builds so that collision checks aren't slowed down.
  206.     /// </summary>
  207.     /// <param name="checkBounds">bounds that were passed in to check for collisions.</param>
  208.     #if UNITY_EDITOR
  209.     void AddCollisionCheck(Bounds checkBounds) {
  210.         lastBoundsCollisionChecks.Enqueue(checkBounds);
  211.         if (lastBoundsCollisionChecks.Count > numCollisionsToSave) {
  212.             lastBoundsCollisionChecks.Dequeue();
  213.         }
  214.     }
  215.     #endif
  216.  
  217.     /// <summary>
  218.     /// Used for visualising collision checks with DrawCollisionChecks.
  219.     /// Automatically removed from builds so that collision checks aren't slowed down.
  220.     /// </summary>
  221.     /// <param name="checkRay">ray that was passed in to check for collisions.</param>
  222.     #if UNITY_EDITOR
  223.     void AddCollisionCheck(Ray checkRay) {
  224.         lastRayCollisionChecks.Enqueue(checkRay);
  225.         if (lastRayCollisionChecks.Count > numCollisionsToSave) {
  226.             lastRayCollisionChecks.Dequeue();
  227.         }
  228.     }
  229.     #endif
  230.  
  231.     /// <summary>
  232.     /// Grow the octree to fit in all objects.
  233.     /// </summary>
  234.     /// <param name="direction">Direction to grow.</param>
  235.     void Grow(Vector3 direction) {
  236.         int xDirection = direction.x >= 0 ? 1 : -1;
  237.         int yDirection = direction.y >= 0 ? 1 : -1;
  238.         int zDirection = direction.z >= 0 ? 1 : -1;
  239.         BoundsOctreeNode<T> oldRoot = rootNode;
  240.         float half = rootNode.BaseLength / 2;
  241.         float newLength = rootNode.BaseLength * 2;
  242.         Vector3 newCenter = rootNode.Center + new Vector3(xDirection * half, yDirection * half, zDirection * half);
  243.  
  244.         // Create a new, bigger octree root node
  245.         rootNode = new BoundsOctreeNode<T>(newLength, minSize, looseness, newCenter);
  246.  
  247.         if (oldRoot.HasAnyObjects())
  248.         {
  249.             // Create 7 new octree children to go with the old root as children of the new root
  250.             int rootPos = GetRootPosIndex(xDirection, yDirection, zDirection);
  251.             BoundsOctreeNode<T>[] children = new BoundsOctreeNode<T>[8];
  252.             for (int i = 0; i < 8; i++)
  253.             {
  254.                 if (i == rootPos)
  255.                 {
  256.                     children[i] = oldRoot;
  257.                 }
  258.                 else
  259.                 {
  260.                     xDirection = i % 2 == 0 ? -1 : 1;
  261.                     yDirection = i > 3 ? -1 : 1;
  262.                     zDirection = (i < 2 || (i > 3 && i < 6)) ? -1 : 1;
  263.                     children[i] = new BoundsOctreeNode<T>(rootNode.BaseLength, minSize, looseness, newCenter + new Vector3(xDirection * half, yDirection * half, zDirection * half));
  264.                 }
  265.             }
  266.  
  267.             // Attach the new children to the new root node
  268.             rootNode.SetChildren(children);
  269.         }
  270.     }
  271.  
  272.     /// <summary>
  273.     /// Shrink the octree if possible, else leave it the same.
  274.     /// </summary>
  275.     void Shrink() {
  276.         rootNode = rootNode.ShrinkIfPossible(initialSize);
  277.     }
  278.  
  279.     /// <summary>
  280.     /// Used when growing the octree. Works out where the old root node would fit inside a new, larger root node.
  281.     /// </summary>
  282.     /// <param name="xDir">X direction of growth. 1 or -1.</param>
  283.     /// <param name="yDir">Y direction of growth. 1 or -1.</param>
  284.     /// <param name="zDir">Z direction of growth. 1 or -1.</param>
  285.     /// <returns>Octant where the root node should be.</returns>
  286.     static int GetRootPosIndex(int xDir, int yDir, int zDir) {
  287.         int result = xDir > 0 ? 1 : 0;
  288.         if (yDir < 0) result += 4;
  289.         if (zDir > 0) result += 2;
  290.         return result;
  291.     }
  292. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement